1//************************************************************************** 2//** 3//** ## ## ## ## ## #### #### ### ### 4//** ## ## ## ## ## ## ## ## ## ## #### #### 5//** ## ## ## ## ## ## ## ## ## ## ## ## ## ## 6//** ## ## ######## ## ## ## ## ## ## ## ### ## 7//** ### ## ## ### ## ## ## ## ## ## 8//** # ## ## # #### #### ## ## 9//** 10//** $Id: SorcBall.vc 3794 2008-09-21 20:42:02Z dj_jl $ 11//** 12//** Copyright (C) 1999-2006 Jānis Legzdiņš 13//** 14//** This program is free software; you can redistribute it and/or 15//** modify it under the terms of the GNU General Public License 16//** as published by the Free Software Foundation; either version 2 17//** of the License, or (at your option) any later version. 18//** 19//** This program is distributed in the hope that it will be useful, 20//** but WITHOUT ANY WARRANTY; without even the implied warranty of 21//** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22//** GNU General Public License for more details. 23//** 24//************************************************************************** 25 26class SorcBall : Actor 27 abstract; 28 29// Heresiarch Ball Variables 30// Special1f Previous angle of ball (for woosh) 31// Special2 Countdown of rapid fire (FX4) 32// Args[0] If set, don't play the bounce sound when bouncing 33float BallAngleOffset; 34 35//========================================================================== 36// 37// FloorBounceMissile 38// 39//========================================================================== 40 41void FloorBounceMissile() 42{ 43 Velocity.z = Velocity.z * -0.7; 44 Velocity.x = 2.0 * Velocity.x / 3.0; 45 Velocity.y = 2.0 * Velocity.y / 3.0; 46 if (SightSound && !Args[0]) 47 { 48 PlaySound(SightSound, CHAN_VOICE); 49 } 50} 51 52//========================================================================== 53// 54// GetExplodeParms 55// 56//========================================================================== 57 58void GetExplodeParms(out int damage, out float distance, out byte damageSelf) 59{ 60 // Sorcerer balls 61 distance = 255.0; 62 damage = 255; 63 Args[0] = 1; // don't play bounce 64} 65 66//========================================================================== 67// 68// A_AccelBalls 69// 70// Increase ball orbit speed - actor is ball 71// 72//========================================================================== 73 74final void A_AccelBalls() 75{ 76 EntityEx sorc = Target; 77 78 if (sorc.Args[4] < sorc.Args[2]) 79 { 80 sorc.Args[4]++; 81 } 82 else 83 { 84 sorc.Args[3] = SORC_NORMAL; 85 if (sorc.Args[4] >= SORCBALL_TERMINAL_SPEED && !sorc.bIsPlayer) 86 { 87 // Reached terminal velocity - stop balls 88 Actor(sorc).A_StopBalls(); 89 } 90 } 91} 92 93//========================================================================== 94// 95// A_DecelBalls 96// 97// Decrease ball orbit speed - actor is ball 98// 99//========================================================================== 100 101final void A_DecelBalls() 102{ 103 EntityEx sorc = Target; 104 105 if (sorc.Args[4] > sorc.Args[2]) 106 { 107 sorc.Args[4]--; 108 } 109 else 110 { 111 sorc.Args[3] = SORC_NORMAL; 112 } 113} 114 115//========================================================================== 116// 117// A_SorcUpdateBallAngle 118// 119// Update angle if first ball - actor is ball 120// 121//========================================================================== 122 123final void A_SorcUpdateBallAngle() 124{ 125 if (Class == SorcBall1) 126 { 127 Actor(Target).Special1f = AngleMod360(Actor(Target).Special1f + itof(Target.Args[4])); 128 } 129} 130 131//========================================================================== 132// 133// A_SorcOffense2 134// 135// Actor is ball 136// 137//========================================================================== 138 139final void A_SorcOffense2() 140{ 141 float ang1; 142 EntityEx mo; 143 float delta, index; 144 float dist; 145 146 index = itof(Args[4]) * 360.0 / 256.0; 147 Args[4] = (Args[4] + 15) & 0xff; 148 delta = sin(index) * SORCFX4_SPREAD_ANGLE; 149 ang1 = AngleMod360(Angles.yaw + delta); 150 mo = Actor(Target).SpawnMissileAngle(SorcFX4, ang1, 0.0); 151 if (mo) 152 { 153 Actor(mo).Special2 = 35 * 5 / 2; // 5 seconds 154 dist = Target.Target.DistTo2(mo); 155 dist = dist / mo.Speed; 156 if (dist < 1.0) 157 dist = 1.0; 158 mo.Velocity.z = (Target.Target.Origin.z - mo.Origin.z) / dist; 159 } 160} 161 162//========================================================================== 163// 164// A_CastSorcererSpell 165// 166// Actor is ball. 167// 168//========================================================================== 169 170void A_CastSorcererSpell() 171{ 172} 173 174//========================================================================== 175// 176// A_SorcBallOrbit 177// 178//========================================================================== 179 180final void A_SorcBallOrbit() 181{ 182 if (!Target) 183 { 184 // Heresiarch dissapeared. 185 Destroy(); 186 return; 187 } 188 189 float angle, baseangle; 190 int mode = Target.Args[3]; 191 float dist = Target.Radius - (Radius * 2.0); 192 float prevangle = Special1f; 193 194 if (Target.Health <= 0) 195 SetState(FindState('Pain')); 196 197 baseangle = Actor(Target).Special1f; 198 angle = AngleMod360(baseangle + BallAngleOffset); 199 Angles.yaw = angle; 200 201 switch (mode) 202 { 203 case SORC_NORMAL: // Balls rotating normally 204 A_SorcUpdateBallAngle(); 205 break; 206 case SORC_DECELERATE: // Balls decelerating 207 A_DecelBalls(); 208 A_SorcUpdateBallAngle(); 209 break; 210 case SORC_ACCELERATE: // Balls accelerating 211 A_AccelBalls(); 212 A_SorcUpdateBallAngle(); 213 break; 214 case SORC_STOPPING: // Balls stopping 215 if ((Actor(Target).SpecialCID == Class) && 216 (Target.Args[1] > SORCBALL_SPEED_ROTATIONS) && 217 (fabs(AngleMod180(angle - Target.Angles.yaw)) < 218 15.0 * 45.0 / 16.0)) 219 { 220 // Can stop now 221 Target.Args[3] = SORC_FIRESPELL; 222 Target.Args[4] = 0; 223 // Set angle so ball angle == sorcerer angle 224 Actor(Target).Special1f = AngleMod360(Target.Angles.yaw - 225 BallAngleOffset); 226 } 227 else 228 { 229 A_SorcUpdateBallAngle(); 230 } 231 break; 232 case SORC_FIRESPELL: // Casting spell 233 if (Actor(Target).SpecialCID == Class) 234 { 235 // Put sorcerer into special throw spell anim 236 if (Target.Health > 0) 237 Target.SetState(Target.FindState('Attack1')); 238 239 PlaySound('SorcererSpellCast', CHAN_VOICE, 1.0, ATTN_NONE); 240 241 A_CastSorcererSpell(); 242 } 243 break; 244 case SORC_FIRING_SPELL: 245 if (Actor(Target).SpecialCID == Class) 246 { 247 if (Special2-- <= 0) 248 { 249 // Done rapid firing 250 Target.Args[3] = SORC_STOPPED; 251 // Back to orbit balls 252 if (Target.Health > 0) 253 Target.SetState(Target.FindState('Attack2')); 254 } 255 else 256 { 257 // Do rapid fire spell 258 A_SorcOffense2(); 259 } 260 } 261 break; 262 case SORC_STOPPED: // Balls stopped 263 default: 264 break; 265 } 266 267 if ((angle < prevangle) && (Target.Args[4] == SORCBALL_TERMINAL_SPEED)) 268 { 269 Target.Args[1]++; // Bump rotation counter 270 // Completed full rotation - make woosh sound 271 PlaySound('SorcererBallWoosh', CHAN_VOICE, 1.0, ATTN_NONE); 272 } 273 Special1f = angle; // Set previous angle 274 Origin.x = Target.Origin.x + dist * cos(angle); 275 Origin.y = Target.Origin.y + dist * sin(angle); 276 Origin.z = Target.Origin.z - Target.FloorClip + Target.Height; 277} 278 279//========================================================================== 280// 281// A_SorcBallPop 282// 283// Ball death - spawn stuff. 284// 285//========================================================================== 286 287final void A_SorcBallPop() 288{ 289 PlaySound('SorcererBallPop', CHAN_VOICE, 1.0, ATTN_NONE); 290 bNoGravity = false; 291 Gravity = 0.125; 292 Velocity.x = (Random() * 10.0 - 5.0) * 35.0; 293 Velocity.y = (Random() * 10.0 - 5.0) * 35.0; 294 Velocity.z = (2.0 + Random() * 3.0) * 35.0; 295 Args[4] = BOUNCE_TIME_UNIT; // Bounce time unit 296 Args[3] = 5; // Bounce time in seconds 297} 298 299//========================================================================== 300// 301// A_BounceCheck 302// 303//========================================================================== 304 305final void A_BounceCheck() 306{ 307 if (Args[4]-- <= 0) 308 { 309 if (Args[3]-- <= 0) 310 { 311 SetState(FindState('Death')); 312 PlaySound('SorcererBigBallExplode', CHAN_VOICE, 1.0, ATTN_NONE); 313 } 314 else 315 { 316 Args[4] = BOUNCE_TIME_UNIT; 317 } 318 } 319} 320 321defaultproperties 322{ 323 Radius = 5.0; 324 Height = 5.0; 325 Speed = 350.0; 326 BounceType = BOUNCE_Hexen; 327 bMissile = true; 328 bNoBlockmap = true; 329 bDropOff = true; 330 bNoGravity = true; 331 bNoTeleport = true; 332 bBloodSplatter = true; 333 bNoWallBounceSnd = true; 334 bFullVolDeath = true; 335 SightSound = 'SorcererBallBounce'; 336 DeathSound = 'SorcererBigBallExplode'; 337} 338