From 202bdce0e2e7562597d4e83234eeb7da54e02e71 Mon Sep 17 00:00:00 2001 From: Marisa Kirisame Date: Sat, 20 Jun 2020 22:20:44 +0200 Subject: [PATCH] More finetunin'. Added forced fall damage option. --- cvarinfo.txt | 1 + language.def_menu | 4 +- language.es_menu | 4 +- language.version | 2 +- menudef.txt | 5 ++- zmapinfo.txt | 6 ++- zscript/swwm_cbt.zsc | 6 +-- zscript/swwm_common.zsc | 75 ++++++++++++++++++++++++++++++--- zscript/swwm_deepdarkimpact.zsc | 5 ++- zscript/swwm_hud.zsc | 10 ++--- zscript/swwm_inventory.zsc | 2 +- zscript/swwm_menu.zsc | 4 ++ zscript/swwm_player.zsc | 2 +- 13 files changed, 101 insertions(+), 25 deletions(-) diff --git a/cvarinfo.txt b/cvarinfo.txt index 5df97c615..f3b5c773f 100644 --- a/cvarinfo.txt +++ b/cvarinfo.txt @@ -58,3 +58,4 @@ user noarchive bool swwm_cbttime = false; // debug: times how long a reload take server bool swwm_crouchjump = false; // allows crouch-jumping (which looks weird af but some maps may need it) user noarchive int swwm_cbtlast = 0; // last selected ammo for the wallbuster server bool swwm_cbtall = true; // wallbuster breaks any wall, not just movable ones +server bool swwm_doomfall = false; // monsters take fall damage outside of hexen diff --git a/language.def_menu b/language.def_menu index 0271dccbe..31664c03d 100644 --- a/language.def_menu +++ b/language.def_menu @@ -100,6 +100,7 @@ SWWM_FUZZ = "Enable Animated Menu BG"; SWWM_BUSTERPAUSE = "Pause on Wallbuster Reload"; SWWM_CROUCHJUMP = "Allow Crouch-Jump"; SWWM_CBTALL = "Unlimited Wallbuster Destruction"; +SWWM_DOOMFALL = "Force Monster Fall Damage"; TOOLTIP_SWWM_VOICETYPE = "Sets the voice pack for the player."; TOOLTIP_SWWM_MUTEVOICE = "Control what gets muted, if you'd rather have a more silent protagonist."; TOOLTIP_SWWM_FLASHSTRENGTH = "Screen flashes usually happen when firing some weapons, you can lower this if these effects are harmful for you."; @@ -146,6 +147,7 @@ TOOLTIP_SWWM_FUZZ = "Toggling this off is recommended if you're recording/stream TOOLTIP_SWWM_CBTPAUSE = "The game will pause while the Wallbuster reload menu is open (only in singleplayer)."; TOOLTIP_SWWM_CROUCHJUMP = "Allow the player to crouch-jump (which may be needed on some maps). Note that there are no animations for this and it'll look weird."; TOOLTIP_SWWM_CBTALL = "Allows the Wallbuster to break pretty much anything, not just doors and platforms. Note that this can break some maps, which is why this option is here."; +TOOLTIP_SWWM_DOOMFALL = "Makes monsters take fall damage outside of Hexen."; // knowledge base SWWM_COMINGSOON = "(coming soon)"; SWWM_MISSTAB = "Mission"; @@ -328,4 +330,4 @@ SWWM_BUSTERKEYS = "\cfBackspace:\c- Clear Ammo\n" "\cfEnter:\c- Initiate Reload\n" "\cfEnter (if empty):\c- Full Reload\n" -"\cfEsc:\c- Cancel Reload\n"; \ No newline at end of file +"\cfEsc:\c- Cancel Reload\n"; diff --git a/language.es_menu b/language.es_menu index f7afa5f4c..50b0a4ef2 100644 --- a/language.es_menu +++ b/language.es_menu @@ -97,6 +97,7 @@ SWWM_FUZZ = "Habilitar Fondo Animado de Menú"; SWWM_BUSTERPAUSE = "Pausar en Regarga de Wallbuster"; SWWM_CROUCHJUMP = "Permitir Salto Agachado"; SWWM_CBTALL = "Destrucción Ilimitada de Wallbuster"; +SWWM_DOOMFALL = "Forzar Daño por Caída en Monstruos"; TOOLTIP_SWWM_VOICETYPE = "Selecciona el pack de voz para el jugador."; TOOLTIP_SWWM_MUTEVOICE = "Controla lo que se mutea, si prefieres tener un protagonista más silencioso."; TOOLTIP_SWWM_FLASHSTRENGTH = "Los destellos en pantalla suelen ocurrir al disparar algunas armas, puedes reducirlo si este tipo de effectos te causan malestar."; @@ -143,6 +144,7 @@ TOOLTIP_SWWM_FUZZ = "Desactivar ésto es recomendado si estás grabando o hacien TOOLTIP_SWWM_CBTPAUSE = "El juego será pausado mientras el menú de recarga de Wallbuster está abierto (sólo en modo de un jugador)."; TOOLTIP_SWWM_CROUCHJUMP = "Permite al jugador agacharse saltando (lo cual puede ser necesario en algunos mapas). Ten en cuenta que no hay animaciones para esto, así que se verá raro."; TOOLTIP_SWWM_CBTALL = "Permite a la Wallbuster romper casi cualquier cosa, no solo puertas y plataformas. Ten en cuenta que esto puede romper algunos mapas, razón de que haya esta opción."; +TOOLTIP_SWWM_DOOMFALL = "Hace que los monstruos reciban daño por caída fuera de Hexen."; // knowledge base SWWM_COMINGSOON = "(próximamente)"; SWWM_MISSTAB = "Misión"; @@ -325,4 +327,4 @@ SWWM_BUSTERKEYS = "\cfRetroceso:\c- Limpiar Munición\n" "\cfEnter:\c- Iniciar Recarga\n" "\cfEnter (si vacío):\c- Recarga Completa\n" -"\cfEsc:\c- Calcelar recarga\n"; \ No newline at end of file +"\cfEsc:\c- Calcelar recarga\n"; diff --git a/language.version b/language.version index dded12a8d..a5b41203b 100644 --- a/language.version +++ b/language.version @@ -1,2 +1,2 @@ [default] -SWWM_MODVER="\cxSWWM GZ\c- r366 (Sat 20 Jun 17:21:12 CEST 2020)"; +SWWM_MODVER="\chSWWM \cwGZ\c- r367 (Sat 20 Jun 22:20:44 CEST 2020)"; diff --git a/menudef.txt b/menudef.txt index 695632d03..efdc56540 100644 --- a/menudef.txt +++ b/menudef.txt @@ -62,6 +62,9 @@ OptionMenu "SWWMOptionMenu" StaticText "$SWWM_BTITLE", "Gold" Option "$SWWM_EXTRAALERT", "swwm_extraalert", "YesNo" Option "$SWWM_BOSSENHANCE", "swwm_upgradebosses", "YesNo" + Option "$SWWM_DOOMFALL", "swwm_doomfall", "YesNo" + Option "$SWWM_BALLUSE", "swwm_balluse", "YesNo" + Option "$SWWM_CBTALL", "swwm_cbtall", "YesNo" StaticText " " StaticText "$SWWM_ITITLE", "Gold" Option "$SWWM_ARMORUSE", "swwm_autousearmor", "YesNo" @@ -69,8 +72,6 @@ OptionMenu "SWWMOptionMenu" Option "$SWWM_AMMOUSE", "swwm_autouseammo", "YesNo" Option "$SWWM_EARBUSTER", "swwm_earbuster", "YesNo" Option "$SWWM_BUSTERPAUSE", "swwm_cbtpause", "YesNo" - Option "$SWWM_BALLUSE", "swwm_balluse", "YesNo" - Option "$SWWM_CBTALL", "swwm_cbtall", "YesNo" StaticText " " StaticText "$SWWM_CTITLE", "Gold" Option "$SWWM_SKEYS", "swwm_sharekeys", "YesNo" diff --git a/zmapinfo.txt b/zmapinfo.txt index f60cd602d..da6c1d535 100644 --- a/zmapinfo.txt +++ b/zmapinfo.txt @@ -30,9 +30,11 @@ GameInfo "SilverBullet", "CandyGun", "YnykronArtifact" - // make easter egg not stutter + // precache long sounds PrecacheSounds = "wallbuster/olddays", - "wallbuster/cbt" + "wallbuster/cbt", + "ynykron/idle", + "ynykron/ready" } Map TITLEMAP "SWWM GZ - Title Map" diff --git a/zscript/swwm_cbt.zsc b/zscript/swwm_cbt.zsc index b41aad065..a19083e2b 100644 --- a/zscript/swwm_cbt.zsc +++ b/zscript/swwm_cbt.zsc @@ -457,7 +457,7 @@ Class BusterWall : Thinker if ( busted ) { busttics++; - if ( busttics > 16 ) + if ( busttics > 12 ) { Destroy(); return; @@ -485,7 +485,7 @@ Class BusterWall : Thinker if ( level.PointInSector(spot.xy) != hitsector ) continue; spot += (FRandom[Wallbuster](-step.x,step.x),FRandom[Wallbuster](-step.y,step.y),FRandom[Wallbuster](-step.z,step.z)); if ( !level.IsPointInLevel(spot) ) continue; - if ( initial || !(busttics%3) ) + if ( (initial || !(busttics%3)) && !Random[Wallbuster](0,2) ) { Vector3 pvel = (bustdir+(FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.))).unit()*FRandom[Wallbuster](-2.,8.); let s = Actor.Spawn("SWWMSmoke",spot); @@ -508,7 +508,7 @@ Class BusterWall : Thinker Vector3 pvel = (bustdir+(FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.6))).unit()*FRandom[Wallbuster](2.,16.); let s = Actor.Spawn("SWWMChip",spot); s.vel = pvel; - s.scale *= FRandom[Wallbuster](2.,4.); + s.scale *= FRandom[Wallbuster](1.2,2.4); s.A_SetTranslation('Rubble'); } } diff --git a/zscript/swwm_common.zsc b/zscript/swwm_common.zsc index aea9951f8..eccd5bf1d 100644 --- a/zscript/swwm_common.zsc +++ b/zscript/swwm_common.zsc @@ -1081,8 +1081,11 @@ Class SWWMCombatTracker : Thinker int newhealth = mytarget.Health; if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE ) { + bool straifu = false; + if ( ((gameinfo.gametype&GAME_Strife) && !mytarget.bINCOMBAT && !mytarget.bJUSTATTACKED) || (mytarget is 'Beggar') || (mytarget is 'Peasant') ) + straifu = true; // enemies within 2000mu that have us as target - if ( mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && mytarget.CheckSight(mytarget.target) && (mytarget.Vec3To(mytarget.target).length() < 2000) ) updated = level.maptime+70; + if ( mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && mytarget.CheckSight(mytarget.target) && (mytarget.Vec3To(mytarget.target).length() < 2000) && !straifu ) updated = level.maptime+70; // players (but not voodoo dolls), always visible in sp/coop if ( !deathmatch && mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35; // any visible enemies within 600mu @@ -1558,7 +1561,7 @@ Class SWWMChip : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - deadtimer = 0; + deadtimer = Random[Junk](-30,30); rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1); frame = Random[Junk](0,5); scale *= Frandom[Junk](0.8,1.2); @@ -1621,7 +1624,7 @@ Class FancyConfetti : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - deadtimer = 0; + deadtimer = Random[Junk](-30,30); anglevel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1); pitchvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1); rollvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1); @@ -2062,6 +2065,59 @@ Class UglyBoyGetsFuckedUp : Thinker } } +// Gotta force it someway +Class ForcedFallerDamager : Thinker +{ + Actor mybody, instigator; + double lastvelz; + bool wasflying; + + static void TrackBody( Actor b ) + { + if ( !b ) return; + let ffd = new("ForcedFallerDamager"); + ffd.ChangeStatNum(STAT_USER); + ffd.mybody = b; + ffd.lastvelz = b.vel.z; + ffd.wasflying = ((b.pos.z>b.floorz)&&b.TestMobjZ()); + } + static void SetInstigator( Actor b, Actor whomst ) + { + if ( !b || !whomst ) return; + let ti = ThinkerIterator.Create("ForcedFallerDamager",STAT_USER); + ForcedFallerDamager ffd; + while ( ffd = ForcedFallerDamager(ti.Next()) ) + { + if ( ffd.mybody != b ) continue; + ffd.instigator = whomst; + break; + } + } + override void Tick() + { + if ( !mybody ) + { + Destroy(); + return; + } + bool isflying = ((mybody.pos.z>mybody.floorz)&&mybody.TestMobjZ()); + if ( wasflying && !isflying && (lastvelz < -23) && !level.monsterfallingdamage && !(mybody.floorsector.flags&Sector.SECF_NOFALLINGDAMAGE) && swwm_doomfall ) + { + // big hurt + int dmg; + if ( lastvelz < -35 ) dmg = Actor.TELEFRAG_DAMAGE; + else dmg = int((-lastvelz-23)*6); + mybody.DamageMobj(instigator,instigator,dmg,'Falling'); + } + if ( !isflying && wasflying ) instigator = null; + wasflying = isflying; + lastvelz = mybody.vel.z; + // wait until body is dead + if ( mybody.Health > 0 ) return; + Destroy(); + } +} + // Handler responsible for item replacements and whatever else Class SWWMHandler : EventHandler { @@ -2605,6 +2661,8 @@ Class SWWMHandler : EventHandler break; } } + // fall dmg + ForcedFallerDamager.SetInstigator(e.Thing,e.DamageSource); } if ( e.Thing.player ) { @@ -2621,7 +2679,7 @@ Class SWWMHandler : EventHandler } if ( e.DamageSource && (e.DamageSource != e.Thing) ) { - if ( (e.DamageSource.bISMONSTER || e.DamageSource.player) && (e.Thing == players[consoleplayer].mo) && (e.Thing.Health > 0) ) + if ( (e.DamageSource.bISMONSTER || e.DamageSource.player || (e.DamageSource is 'ScriptedMarine')) && (e.Thing == players[consoleplayer].mo) && (e.Thing.Health > 0) ) { if ( !lastcombat || (gametic > lastcombat+40) ) { @@ -2632,7 +2690,7 @@ Class SWWMHandler : EventHandler highesttic = gametic; } // friendly fire lines only fire up if we didn't kill them right away (because then the teamkill line should take priority) - if ( (e.DamageSource == players[consoleplayer].mo) && (e.Thing.bISMONSTER || e.Thing.player) && (e.Thing.Health > 0) ) + if ( (e.DamageSource == players[consoleplayer].mo) && (e.Thing.bISMONSTER || e.Thing.player || (e.Thing is 'ScriptedMarine')) && (e.Thing.Health > 0) ) { // make sure it's not a moth, because otherwise they won't shut up about accidentally hurting them (it happens a lot) if ( (e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.Thing)) && !(e.Thing is 'LampMoth') ) @@ -2644,7 +2702,7 @@ Class SWWMHandler : EventHandler } } if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return; - if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill ) return; + if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill && !(e.Thing is 'ScriptedMarine') ) return; if ( (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) ) { let s = SWWMStats.Find(e.DamageSource.player); @@ -2664,7 +2722,8 @@ Class SWWMHandler : EventHandler lastcombat = AddOneliner("scorekill",1,15); } } - if ( !e.Thing.default.bCountKill ) // no credits + // no credits unless it's a counted kill or marine (that isn't friendly) + if ( e.Thing.IsFriend(e.DamageSource) || (!e.Thing.default.bCountKill && !(e.Thing is 'ScriptedMarine')) ) return; int pnum = e.DamageSource.PlayerNumber(); if ( level.maptime < (lastkill[pnum]+5*Thinker.TICRATE) ) @@ -2801,6 +2860,8 @@ Class SWWMHandler : EventHandler { if ( e.Thing is 'Inventory' ) CopyFloatBob(e.Thing); + if ( e.Thing.bISMONSTER && !level.monsterfallingdamage ) + ForcedFallerDamager.TrackBody(e.Thing); if ( e.Thing is 'Key' ) { DoKeyTagFix(e.Thing); diff --git a/zscript/swwm_deepdarkimpact.zsc b/zscript/swwm_deepdarkimpact.zsc index 2624f5dc7..6ddd29008 100644 --- a/zscript/swwm_deepdarkimpact.zsc +++ b/zscript/swwm_deepdarkimpact.zsc @@ -367,7 +367,10 @@ Class DeepImpact : SWWMWeapon Vector3 rdir = level.Vec3Diff(origin,m.pos).unit(); if ( LineTrace(atan2(rdir.y,rdir.x),rdist,asin(-rdir.z),TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y) || (rdist > 150) || (rdir dot x < 0.7) ) continue; m.speed = m.vel.length(); - m.vel = m.speed*(m.vel.unit()*.2+rdir).unit(); + m.vel = m.speed*1.5*(m.vel.unit()*.2+rdir).unit(); + Vector3 ndir = m.vel.unit(); + m.angle = atan2(ndir.y,ndir.x); + m.pitch = asin(-ndir.z); if ( m.target == self ) continue; if ( m.bSEEKERMISSILE ) m.tracer = m.target; m.target = self; diff --git a/zscript/swwm_hud.zsc b/zscript/swwm_hud.zsc index 916b80a80..074473e19 100644 --- a/zscript/swwm_hud.zsc +++ b/zscript/swwm_hud.zsc @@ -695,9 +695,9 @@ Class SWWMStatusBar : BaseStatusBar { // damage falls down int initspd = (128-snum.seed); - if ( initspd >= 0 && initspd < 32 ) initspd = 32; - if ( initspd < 0 && initspd > -32 ) initspd = -32; - int boostup = 64+snum.seed2/2; + //if ( initspd >= 0 && initspd < 32 ) initspd = 32; + //if ( initspd < 0 && initspd > -32 ) initspd = -32; + int boostup = 64+snum.seed2; fo.x = (.05*initspd)*((snum.initialspan-(snum.lifespan-fractic))**.8); fo.y = -((snum.initialspan-(snum.lifespan-fractic))**1.5)+boostup*sin((90./snum.initialspan)*(level.maptime+fractic-snum.starttic)); } @@ -705,8 +705,8 @@ Class SWWMStatusBar : BaseStatusBar { // health falls up (?) int initspd = (128-snum.seed); - if ( initspd >= 0 && initspd < 32 ) initspd = 32; - if ( initspd < 0 && initspd > -32 ) initspd = -32; + //if ( initspd >= 0 && initspd < 32 ) initspd = 32; + //if ( initspd < 0 && initspd > -32 ) initspd = -32; int boostup = 16+snum.seed2/4; fo.x = (.15*initspd)*((snum.initialspan-(snum.lifespan-fractic))**.6); fo.y = ((snum.initialspan-(snum.lifespan-fractic))**1.2)-boostup*sin((90./snum.initialspan)*(level.maptime+fractic-snum.starttic)); diff --git a/zscript/swwm_inventory.zsc b/zscript/swwm_inventory.zsc index c594ead30..2d3ab50f5 100644 --- a/zscript/swwm_inventory.zsc +++ b/zscript/swwm_inventory.zsc @@ -371,7 +371,7 @@ Class SWWMCasing : Actor abstract override void PostBeginPlay() { Super.PostBeginPlay(); - deadtimer = 0; + deadtimer = Random[Junk](-30,30); pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1); anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1); heat = 1.0; diff --git a/zscript/swwm_menu.zsc b/zscript/swwm_menu.zsc index f1343453a..5b5798fbd 100644 --- a/zscript/swwm_menu.zsc +++ b/zscript/swwm_menu.zsc @@ -317,6 +317,7 @@ Class SWWMKnowledgeBaseMenu : GenericMenu if ( gameinfo.gametype&GAME_Doom ) str = StringTable.Localize("$SWWM_MISSION_DOOM"); else if ( gameinfo.gametype&GAME_Heretic ) str = StringTable.Localize("$SWWM_MISSION_HERETIC"); else if ( gameinfo.gametype&GAME_Hexen ) str = StringTable.Localize("$SWWM_MISSION_HEXEN"); + else if ( gameinfo.gametype&GAME_Strife ) str = StringTable.Localize(players[consoleplayer].logtext); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); if ( (l.Count() > 28) && (sel0 < l.Count()-28) ) @@ -693,6 +694,7 @@ Class SWWMKnowledgeBaseMenu : GenericMenu if ( gameinfo.gametype&GAME_Doom ) str = StringTable.Localize("$SWWM_MISSION_DOOM"); else if ( gameinfo.gametype&GAME_Heretic ) str = StringTable.Localize("$SWWM_MISSION_HERETIC"); else if ( gameinfo.gametype&GAME_Hexen ) str = StringTable.Localize("$SWWM_MISSION_HEXEN"); + else if ( gameinfo.gametype&GAME_Strife ) str = StringTable.Localize(players[consoleplayer].logtext); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar @@ -1180,6 +1182,7 @@ Class SWWMKnowledgeBaseMenu : GenericMenu if ( gameinfo.gametype&GAME_Doom ) str = StringTable.Localize("$SWWM_MISSION_DOOM"); else if ( gameinfo.gametype&GAME_Heretic ) str = StringTable.Localize("$SWWM_MISSION_HERETIC"); else if ( gameinfo.gametype&GAME_Hexen ) str = StringTable.Localize("$SWWM_MISSION_HEXEN"); + else if ( gameinfo.gametype&GAME_Strife ) str = StringTable.Localize(players[consoleplayer].logtext); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar @@ -1881,6 +1884,7 @@ Class SWWMKnowledgeBaseMenu : GenericMenu if ( gameinfo.gametype&GAME_Doom ) str = StringTable.Localize("$SWWM_MISSION_DOOM"); else if ( gameinfo.gametype&GAME_Heretic ) str = StringTable.Localize("$SWWM_MISSION_HERETIC"); else if ( gameinfo.gametype&GAME_Hexen ) str = StringTable.Localize("$SWWM_MISSION_HEXEN"); + else if ( gameinfo.gametype&GAME_Strife ) str = StringTable.Localize(players[consoleplayer].logtext); else str = StringTable.Localize("$SWWM_MISSION_NONE"); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); diff --git a/zscript/swwm_player.zsc b/zscript/swwm_player.zsc index 9518e940d..9edcee76a 100644 --- a/zscript/swwm_player.zsc +++ b/zscript/swwm_player.zsc @@ -1889,7 +1889,7 @@ Class DemolitionistShockwave : Actor let r = Spawn("DemolitionistRadiusShockwave",Vec3Angle(10,i)); r.target = target; r.angle = i; - r.vel.xy = (cos(i),sin(i))*r.speed; + r.vel.xy = (cos(i),sin(i))*(r.speed+min(special1*.15,20)); r.alpha *= .1+min(special1*.03,.9); } for ( int i=0; i<360; i+=5 )