1 (*
2 * Hedgewars, a free turn based strategy game
3 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 2 of the License
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 *)
18
19 {$INCLUDE "options.inc"}
20 unit uGearsList;
21
22 interface
23 uses uFloat, uTypes, SDLh;
24
AddGearnull25 function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;
AddGearnull26 function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer, newUid: LongWord): PGear;
27 procedure DeleteGear(Gear: PGear);
28 procedure InsertGearToList(Gear: PGear);
29 procedure RemoveGearFromList(Gear: PGear);
30
31 var curHandledGear: PGear;
32
33 implementation
34
35 uses uRandom, uUtils, uConsts, uVariables, uAmmos, uTeams, uStats,
36 uTextures, uScript, uRenderUtils, uAI, uCollisions,
37 uGearsRender, uGearsUtils, uDebug;
38
39 const
40 GearKindAmmoTypeMap : array [TGearType] of TAmmoType = (
41 (* gtFlame *) amNothing
42 (* gtHedgehog *) , amNothing
43 (* gtMine *) , amMine
44 (* gtCase *) , amNothing
45 (* gtAirMine *) , amAirMine
46 (* gtExplosives *) , amNothing
47 (* gtGrenade *) , amGrenade
48 (* gtShell *) , amBazooka
49 (* gtGrave *) , amNothing
50 (* gtBee *) , amBee
51 (* gtShotgunShot *) , amShotgun
52 (* gtPickHammer *) , amPickHammer
53 (* gtRope *) , amRope
54 (* gtDEagleShot *) , amDEagle
55 (* gtDynamite *) , amDynamite
56 (* gtClusterBomb *) , amClusterBomb
57 (* gtCluster *) , amClusterBomb
58 (* gtShover *) , amBaseballBat // Shover is only used for baseball bat right now
59 (* gtFirePunch *) , amFirePunch
60 (* gtATStartGame *) , amNothing
61 (* gtATFinishGame *) , amNothing
62 (* gtParachute *) , amParachute
63 (* gtAirAttack *) , amAirAttack
64 (* gtAirBomb *) , amAirAttack
65 (* gtBlowTorch *) , amBlowTorch
66 (* gtGirder *) , amGirder
67 (* gtTeleport *) , amTeleport
68 (* gtSwitcher *) , amSwitch
69 (* gtTarget *) , amNothing
70 (* gtMortar *) , amMortar
71 (* gtWhip *) , amWhip
72 (* gtKamikaze *) , amKamikaze
73 (* gtCake *) , amCake
74 (* gtSeduction *) , amSeduction
75 (* gtWatermelon *) , amWatermelon
76 (* gtMelonPiece *) , amWatermelon
77 (* gtHellishBomb *) , amHellishBomb
78 (* gtWaterUp *) , amNothing
79 (* gtDrill *) , amDrill
80 (* gtBallGun *) , amBallgun
81 (* gtBall *) , amBallgun
82 (* gtRCPlane *) , amRCPlane
83 (*gtSniperRifleShot *) , amSniperRifle
84 (* gtJetpack *) , amJetpack
85 (* gtMolotov *) , amMolotov
86 (* gtBirdy *) , amBirdy
87 (* gtEgg *) , amBirdy
88 (* gtPortal *) , amPortalGun
89 (* gtPiano *) , amPiano
90 (* gtGasBomb *) , amGasBomb
91 (* gtSineGunShot *) , amSineGun
92 (* gtFlamethrower *) , amFlamethrower
93 (* gtSMine *) , amSMine
94 (* gtPoisonCloud *) , amNothing
95 (* gtHammer *) , amHammer
96 (* gtHammerHit *) , amHammer
97 (* gtResurrector *) , amResurrector
98 (* gtPoisonCloud *) , amNothing
99 (* gtSnowball *) , amSnowball
100 (* gtFlake *) , amNothing
101 (* gtLandGun *) , amLandGun
102 (* gtTardis *) , amTardis
103 (* gtIceGun *) , amIceGun
104 (* gtAddAmmo *) , amNothing
105 (* gtGenericFaller *) , amNothing
106 (* gtKnife *) , amKnife
107 (* gtCreeper *) , amCreeper
108 (* gtMinigun *) , amMinigun
109 (* gtMinigunBullet *) , amMinigun
110 );
111
112
113 var GCounter: LongWord = 0; // this does not get re-initialized, but should be harmless
114
115 const
116 cUsualZ = 500;
117 cOnHHZ = 2000;
118
119 procedure InsertGearToList(Gear: PGear);
120 var tmp, ptmp: PGear;
121 begin
122 tmp:= GearsList;
123 ptmp:= GearsList;
124 while (tmp <> nil) and (tmp^.Z < Gear^.Z) do
125 begin
126 ptmp:= tmp;
127 tmp:= tmp^.NextGear
128 end;
129
130 if ptmp <> tmp then
131 begin
132 Gear^.NextGear:= ptmp^.NextGear;
133 Gear^.PrevGear:= ptmp;
134 if ptmp^.NextGear <> nil then
135 ptmp^.NextGear^.PrevGear:= Gear;
136 ptmp^.NextGear:= Gear
137 end
138 else
139 begin
140 Gear^.NextGear:= GearsList;
141 if Gear^.NextGear <> nil then
142 Gear^.NextGear^.PrevGear:= Gear;
143 GearsList:= Gear;
144 end;
145 end;
146
147
148 procedure RemoveGearFromList(Gear: PGear);
149 begin
150 if (Gear <> GearsList) and (Gear <> nil) and (Gear^.NextGear = nil) and (Gear^.PrevGear = nil) then
151 begin
152 AddFileLog('Attempted to remove Gear #'+inttostr(Gear^.uid)+' from the list twice.');
153 exit
154 end;
155
156 checkFails((Gear = nil) or (curHandledGear = nil) or (Gear = curHandledGear), 'You''re doing it wrong', true);
157
158 if Gear^.NextGear <> nil then
159 Gear^.NextGear^.PrevGear:= Gear^.PrevGear;
160 if Gear^.PrevGear <> nil then
161 Gear^.PrevGear^.NextGear:= Gear^.NextGear
162 else
163 GearsList:= Gear^.NextGear;
164
165 Gear^.NextGear:= nil;
166 Gear^.PrevGear:= nil
167 end;
168
169
AddGearnull170 function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer: LongWord): PGear;
171 begin
172 AddGear:= AddGear(X, Y, Kind, State, dX, dY, Timer, 0);
173 end;
AddGearnull174 function AddGear(X, Y: LongInt; Kind: TGearType; State: Longword; dX, dY: hwFloat; Timer, newUid: LongWord): PGear;
175 var gear: PGear;
176 //c: byte;
177 cakeData: PCakeData;
178 begin
179 if newUid = 0 then
180 inc(GCounter);
181
182 AddFileLog('AddGear: #' + inttostr(GCounter) + ' (' + inttostr(x) + ',' + inttostr(y) + '), d(' + floattostr(dX) + ',' + floattostr(dY) + ') type = ' + EnumToStr(Kind));
183
184
185 New(gear);
186 FillChar(gear^, sizeof(TGear), 0);
187 gear^.X:= int2hwFloat(X);
188 gear^.Y:= int2hwFloat(Y);
189 gear^.Target.X:= NoPointX;
190 gear^.Kind := Kind;
191 gear^.State:= State;
192 gear^.Active:= true;
193 gear^.dX:= dX;
194 gear^.dY:= dY;
195 gear^.doStep:= doStepHandlers[Kind];
196 gear^.CollisionIndex:= -1;
197 gear^.Timer:= Timer;
198 if newUid = 0 then
199 gear^.uid:= GCounter
200 else gear^.uid:= newUid;
201 gear^.SoundChannel:= -1;
202 gear^.ImpactSound:= sndNone;
203 gear^.Density:= _1;
204 // Define ammo association, if any.
205 gear^.AmmoType:= GearKindAmmoTypeMap[Kind];
206 gear^.CollisionMask:= lfAll;
207 gear^.Tint:= $FFFFFFFF;
208 gear^.Data:= nil;
209 gear^.Sticky:= false;
210
211 if CurrentHedgehog <> nil then
212 begin
213 gear^.Hedgehog:= CurrentHedgehog;
214 if (CurrentHedgehog^.Gear <> nil) and (hwRound(CurrentHedgehog^.Gear^.X) = X) and (hwRound(CurrentHedgehog^.Gear^.Y) = Y) then
215 gear^.CollisionMask:= lfNotCurHogCrate
216 end;
217
218 if (Ammoz[Gear^.AmmoType].Ammo.Propz and ammoprop_NeedTarget <> 0) then
219 gear^.Z:= cHHZ+1
220 else gear^.Z:= cUsualZ;
221
222 // set gstInBounceEdge if gear spawned inside the bounce world edge
223 if WorldEdge = weBounce then
224 if (hwRound(gear^.X) - Gear^.Radius < leftX) or (hwRound(gear^.X) + Gear^.Radius > rightX) then
225 case gear^.Kind of
226 // list all gears here that could collide with the bounce world edge
227 gtHedgehog,
228 gtFlame,
229 gtMine,
230 gtAirBomb,
231 gtDrill,
232 gtNapalmBomb,
233 gtCase,
234 gtAirMine,
235 gtExplosives,
236 gtGrenade,
237 gtShell,
238 gtBee,
239 gtDynamite,
240 gtClusterBomb,
241 gtMelonPiece,
242 gtCluster,
243 gtMortar,
244 gtKamikaze,
245 gtCake,
246 gtWatermelon,
247 gtGasBomb,
248 gtHellishBomb,
249 gtBall,
250 gtRCPlane,
251 gtSniperRifleShot,
252 gtShotgunShot,
253 gtDEagleShot,
254 gtSineGunShot,
255 gtMinigunBullet,
256 gtEgg,
257 gtPiano,
258 gtSMine,
259 gtSnowball,
260 gtKnife,
261 gtCreeper,
262 gtMolotov,
263 gtFlake,
264 gtGrave,
265 gtPortal,
266 gtTarget:
267 gear^.State := gear^.State or gstInBounceEdge;
268 end;
269
270 case Kind of
271 gtFlame: Gear^.Boom := 2; // some additional expl in there are x3, x4 this
272 gtHedgehog: Gear^.Boom := 30;
273 gtMine: Gear^.Boom := 50;
274 gtCase: Gear^.Boom := 25;
275 gtAirMine: Gear^.Boom := 30;
276 gtExplosives: Gear^.Boom := 75;
277 gtGrenade: Gear^.Boom := 50;
278 gtShell: Gear^.Boom := 50;
279 gtBee: Gear^.Boom := 50;
280 gtShotgunShot: Gear^.Boom := 25;
281 gtPickHammer: Gear^.Boom := 6;
282 // gtRope: Gear^.Boom := 2; could be funny to have rope attaching to hog deal small amount of dmg?
283 gtDEagleShot: Gear^.Boom := 7;
284 gtDynamite: Gear^.Boom := 75;
285 gtClusterBomb: Gear^.Boom := 20;
286 gtMelonPiece,
287 gtCluster: Gear^.Boom := Timer;
288 gtShover: Gear^.Boom := 30;
289 gtFirePunch: Gear^.Boom := 30;
290 gtAirBomb: Gear^.Boom := 30;
291 gtBlowTorch: Gear^.Boom := 2;
292 gtMortar: Gear^.Boom := 20;
293 gtWhip: Gear^.Boom := 30;
294 gtKamikaze: Gear^.Boom := 30; // both shove and explosion
295 gtCake: Gear^.Boom := cakeDmg; // why is cake damage a global constant
296 gtWatermelon: Gear^.Boom := 75;
297 gtHellishBomb: Gear^.Boom := 90;
298 gtDrill: if Gear^.State and gsttmpFlag = 0 then
299 Gear^.Boom := 50
300 else Gear^.Boom := 30;
301 gtBall: Gear^.Boom := 40;
302 gtRCPlane: Gear^.Boom := 25;
303 // sniper rifle is distance linked, this Boom is just an arbitrary scaling factor applied to timer-based-damage
304 // because, eh, why not..
305 gtSniperRifleShot: Gear^.Boom := 100000;
306 gtEgg: Gear^.Boom := 10;
307 gtPiano: Gear^.Boom := 80;
308 gtGasBomb: Gear^.Boom := 20;
309 gtSineGunShot: Gear^.Boom := 35;
310 gtSMine: Gear^.Boom := 30;
311 gtSnowball: Gear^.Boom := 200000; // arbitrary scaling for the shove
312 gtHammer: if cDamageModifier > _1 then // scale it based on cDamageModifier?
313 Gear^.Boom := 2
314 else Gear^.Boom := 3;
315 gtPoisonCloud: Gear^.Boom := 20;
316 gtKnife: Gear^.Boom := 40000; // arbitrary scaling factor since impact-based
317 gtCreeper: Gear^.Boom := 100;
318 gtMinigunBullet: Gear^.Boom := 2;
319 end;
320
321 case Kind of
322 gtGrenade,
323 gtClusterBomb,
324 gtGasBomb: begin
325 gear^.ImpactSound:= sndGrenadeImpact;
326 gear^.nImpactSounds:= 1;
327 gear^.AdvBounce:= 1;
328 gear^.Radius:= 5;
329 gear^.Elasticity:= _0_8;
330 gear^.Friction:= _0_8;
331 gear^.Density:= _1_5;
332 gear^.RenderTimer:= true;
333 if gear^.Timer = 0 then
334 gear^.Timer:= 3000
335 end;
336 gtWatermelon: begin
337 gear^.ImpactSound:= sndMelonImpact;
338 gear^.nImpactSounds:= 1;
339 gear^.AdvBounce:= 1;
340 gear^.Radius:= 6;
341 gear^.Elasticity:= _0_8;
342 gear^.Friction:= _0_995;
343 gear^.Density:= _2;
344 gear^.RenderTimer:= true;
345 if gear^.Timer = 0 then
346 gear^.Timer:= 3000
347 end;
348 gtMelonPiece: begin
349 gear^.AdvBounce:= 1;
350 gear^.Density:= _2;
351 gear^.Elasticity:= _0_8;
352 gear^.Friction:= _0_995;
353 gear^.Radius:= 4
354 end;
355 gtHedgehog: begin
356 gear^.AdvBounce:= 1;
357 gear^.Radius:= cHHRadius;
358 gear^.Elasticity:= _0_35;
359 gear^.Friction:= _0_999;
360 gear^.Angle:= cMaxAngle div 2;
361 gear^.Density:= _3;
362 gear^.Z:= cHHZ;
363 if (GameFlags and gfAISurvival) <> 0 then
364 if gear^.Hedgehog^.BotLevel > 0 then
365 gear^.Hedgehog^.Effects[heResurrectable] := 1;
366 if (GameFlags and gfArtillery) <> 0 then
367 gear^.Hedgehog^.Effects[heArtillery] := 1;
368 // this would presumably be set in the frontend
369 // if we weren't going to do that yet, would need to reinit GetRandom
370 // oh, and, randomising slightly R and B might be nice too.
371 //gear^.Tint:= $fa00efff or ((random(80)+128) shl 16)
372 //gear^.Tint:= $faa4efff
373 //gear^.Tint:= (($e0+random(32)) shl 24) or
374 // ((random(80)+128) shl 16) or
375 // (($d5+random(32)) shl 8) or $ff
376 {c:= GetRandom(32);
377 gear^.Tint:= (($e0+c) shl 24) or
378 ((GetRandom(90)+128) shl 16) or
379 (($d5+c) shl 8) or $ff}
380 end;
381 gtParachute: begin
382 gear^.Tag:= 1; // hog face dir. 1 = right, -1 = left
383 gear^.Z:= cCurrHHZ;
384 end;
385 gtShell: begin
386 gear^.Elasticity:= _0_8;
387 gear^.Friction:= _0_8;
388 gear^.Radius:= 4;
389 gear^.Density:= _1;
390 gear^.AdvBounce:= 1;
391 end;
392 gtSnowball: begin
393 gear^.ImpactSound:= sndMudballImpact;
394 gear^.nImpactSounds:= 1;
395 gear^.Radius:= 4;
396 gear^.Density:= _0_5;
397 gear^.AdvBounce:= 1;
398 gear^.Elasticity:= _0_8;
399 gear^.Friction:= _0_8;
400 end;
401
402 gtFlake: begin
403 with Gear^ do
404 begin
405 Pos:= 0;
406 Radius:= 1;
407 DirAngle:= random(360);
408 Sticky:= true;
409 if State and gstTmpFlag = 0 then
410 begin
411 dx.isNegative:= GetRandom(2) = 0;
412 dx.QWordValue:= QWord($40DA) * GetRandom(10000) * 8;
413 dy.isNegative:= false;
414 dy.QWordValue:= QWord($3AD3) * GetRandom(7000) * 8;
415 if GetRandom(2) = 0 then
416 dx := -dx;
417 Tint:= $FFFFFFFF
418 end
419 else
420 Tint:= (ExplosionBorderColor shr RShift and $FF shl 24) or
421 (ExplosionBorderColor shr GShift and $FF shl 16) or
422 (ExplosionBorderColor shr BShift and $FF shl 8) or $FF;
423 State:= State or gstInvisible;
424 // use health field to store current frameticks
425 if vobFrameTicks > 0 then
426 Health:= random(vobFrameTicks)
427 else
428 Health:= 0;
429 // use timer to store currently displayed frame index
430 if gear^.Timer = 0 then Timer:= random(vobFramesCount);
431 Damage:= (random(2) * 2 - 1) * (vobVelocity + random(vobVelocity)) * 8
432 end
433 end;
434 gtGrave: begin
435 gear^.ImpactSound:= sndGraveImpact;
436 gear^.nImpactSounds:= 1;
437 gear^.Radius:= 10;
438 gear^.Elasticity:= _0_6;
439 gear^.Z:= 1;
440 end;
441 gtBee: begin
442 gear^.Radius:= 5;
443 if gear^.Timer = 0 then gear^.Timer:= 500;
444 gear^.RenderTimer:= true;
445 gear^.Elasticity:= _0_9;
446 gear^.Tag:= 0;
447 gear^.State:= Gear^.State or gstSubmersible
448 end;
449 gtSeduction: begin
450 gear^.Radius:= cSeductionDist;
451 end;
452 gtShotgunShot: begin
453 if gear^.Timer = 0 then gear^.Timer:= 900;
454 gear^.Radius:= 2
455 end;
456 gtPickHammer: begin
457 gear^.Radius:= 10;
458 if gear^.Timer = 0 then gear^.Timer:= 4000
459 end;
460 gtHammerHit: begin
461 gear^.Radius:= 8;
462 if gear^.Timer = 0 then gear^.Timer:= 125
463 end;
464 gtRope: begin
465 gear^.Radius:= 3;
466 gear^.Friction:= _450 * _0_01 * cRopePercent;
467 RopePoints.Count:= 0;
468 gear^.Tint:= $D8D8D8FF;
469 gear^.Tag:= 0; // normal rope render
470 gear^.CollisionMask:= lfNotCurHogCrate //lfNotObjMask or lfNotHHObjMask;
471 end;
472 gtMine: begin
473 gear^.ImpactSound:= sndMineImpact;
474 gear^.nImpactSounds:= 1;
475 gear^.Health:= 10;
476 gear^.State:= gear^.State or gstMoving;
477 gear^.Radius:= 2;
478 gear^.Elasticity:= _0_55;
479 gear^.Friction:= _0_995;
480 gear^.Density:= _1;
481 if gear^.Timer = 0 then
482 begin
483 if cMinesTime < 0 then
484 begin
485 gear^.Timer:= getrandom(51)*100;
486 gear^.Karma:= 1;
487 end
488 else
489 gear^.Timer:= cMinesTime;
490 end;
491 gear^.RenderTimer:= true;
492 end;
493 gtAirMine: begin
494 gear^.AdvBounce:= 1;
495 gear^.ImpactSound:= sndAirMineImpact;
496 gear^.nImpactSounds:= 1;
497 gear^.Health:= 30;
498 gear^.State:= gear^.State or gstMoving or gstNoGravity or gstSubmersible;
499 gear^.Radius:= 8;
500 gear^.Elasticity:= _0_55;
501 gear^.Friction:= _0_995;
502 gear^.Density:= _1;
503 gear^.Angle:= 175; // Radius at which air bombs will start "seeking". $FFFFFFFF = unlimited. check is skipped.
504 gear^.Power:= cMaxWindSpeed.QWordValue div 2; // hwFloat converted. 1/2 g default. defines the "seek" speed when a gear is in range.
505 gear^.Pos:= cMaxWindSpeed.QWordValue * 3 div 2; // air friction. slows it down when not hitting stuff
506 gear^.Tag:= 0;
507 if gear^.Timer = 0 then
508 begin
509 if cMinesTime < 0 then
510 begin
511 gear^.Timer:= getrandom(13)*100;
512 gear^.Karma:= 1;
513 end
514 else
515 gear^.Timer:= cMinesTime div 4;
516 end;
517 gear^.RenderTimer:= true;
518 gear^.WDTimer:= gear^.Timer
519 end;
520 gtSMine: begin
521 gear^.Health:= 10;
522 gear^.State:= gear^.State or gstMoving;
523 gear^.Radius:= 2;
524 gear^.Elasticity:= _0_55;
525 gear^.Friction:= _0_995;
526 gear^.Density:= _1_6;
527 gear^.AdvBounce:= 1;
528 gear^.Sticky:= true;
529 if gear^.Timer = 0 then gear^.Timer:= 500;
530 gear^.RenderTimer:= true;
531 end;
532 gtKnife: begin
533 gear^.ImpactSound:= sndKnifeImpact;
534 gear^.AdvBounce:= 1;
535 gear^.Elasticity:= _0_8;
536 gear^.Friction:= _0_8;
537 gear^.Density:= _4;
538 gear^.Radius:= 7;
539 gear^.Sticky:= true;
540 end;
541 gtCase: begin
542 gear^.ImpactSound:= sndCaseImpact;
543 gear^.nImpactSounds:= 1;
544 gear^.Radius:= 16;
545 gear^.Elasticity:= _0_3;
546 if gear^.Timer = 0 then gear^.Timer:= 500
547 end;
548 gtExplosives: begin
549 gear^.AdvBounce:= 1;
550 if GameType in [gmtDemo, gmtRecord] then
551 gear^.RenderHealth:= true;
552 gear^.ImpactSound:= sndGrenadeImpact;
553 gear^.nImpactSounds:= 1;
554 gear^.Radius:= 16;
555 gear^.Elasticity:= _0_4;
556 gear^.Friction:= _0_995;
557 gear^.Density:= _6;
558 gear^.Health:= cBarrelHealth;
559 gear^.Z:= cHHZ-1
560 end;
561 gtDEagleShot: begin
562 gear^.Radius:= 1;
563 gear^.Health:= 50
564 end;
565 gtSniperRifleShot: begin
566 gear^.Radius:= 1;
567 gear^.Health:= 50
568 end;
569 gtDynamite: begin
570 gear^.Radius:= 3;
571 gear^.Elasticity:= _0_55;
572 gear^.Friction:= _0_03;
573 gear^.Density:= _2;
574 if gear^.Timer = 0 then gear^.Timer:= 5000;
575 end;
576 gtCluster: begin
577 gear^.AdvBounce:= 1;
578 gear^.Elasticity:= _0_8;
579 gear^.Friction:= _0_8;
580 gear^.Radius:= 2;
581 gear^.Density:= _1_5;
582 gear^.RenderTimer:= true
583 end;
584 gtShover: begin
585 gear^.Radius:= 20;
586 gear^.Tag:= 0;
587 gear^.Timer:= 50;
588 end;
589 gtFlame: begin
590 gear^.Tag:= GetRandom(32);
591 gear^.Radius:= 1;
592 gear^.Health:= 5;
593 gear^.Density:= _1;
594 gear^.FlightTime:= 9999999; // determines whether in-air flames do damage. disabled by default
595 if (gear^.dY.QWordValue = 0) and (gear^.dX.QWordValue = 0) then
596 begin
597 gear^.dY:= (getrandomf - _0_8) * _0_03;
598 gear^.dX:= (getrandomf - _0_5) * _0_4
599 end
600 end;
601 gtFirePunch: begin
602 if gear^.Timer = 0 then gear^.Timer:= 3000;
603 gear^.Radius:= 15;
604 gear^.Tag:= Y
605 end;
606 gtAirAttack: begin
607 gear^.Health:= 6;
608 gear^.Damage:= 30;
609 gear^.Z:= cHHZ+2;
610 gear^.Karma:= 0; // for sound effect: 0 = normal, 1 = underwater
611 gear^.Radius:= 150;
612 gear^.FlightTime:= 0; // for timeout in weWrap
613 gear^.Power:= 0; // count number of wraps in weWrap
614 gear^.WDTimer:= 0; // number of required wraps
615 gear^.Density:= _19;
616 gear^.Tint:= gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF
617 end;
618 gtAirBomb: begin
619 gear^.AdvBounce:= 1;
620 gear^.Radius:= 5;
621 gear^.Density:= _2;
622 gear^.Elasticity:= _0_55;
623 gear^.Friction:= _0_995
624 end;
625 gtBlowTorch: begin
626 gear^.Radius:= cHHRadius + cBlowTorchC;
627 if gear^.Timer = 0 then gear^.Timer:= 7500
628 end;
629 gtSwitcher: begin
630 gear^.Z:= cCurrHHZ
631 end;
632 gtTarget: begin
633 gear^.ImpactSound:= sndGrenadeImpact;
634 gear^.nImpactSounds:= 1;
635 gear^.Radius:= 10;
636 gear^.Elasticity:= _0_3;
637 end;
638 gtTardis: begin
639 gear^.Pos:= 1; // tardis phase
640 gear^.Tag:= 0; // 1 = hedgehog died, disappeared, took damage or moved
641 gear^.Z:= cCurrHHZ+1;
642 end;
643 gtMortar: begin
644 gear^.AdvBounce:= 1;
645 gear^.Radius:= 4;
646 gear^.Elasticity:= _0_2;
647 gear^.Friction:= _0_08;
648 gear^.Density:= _1;
649 end;
650 gtWhip: gear^.Radius:= 20;
651 gtHammer: gear^.Radius:= 20;
652 gtKamikaze: begin
653 gear^.Health:= 2048;
654 gear^.Radius:= 20
655 end;
656 gtCake: begin
657 gear^.Health:= 2048;
658 gear^.Radius:= 7;
659 gear^.Z:= cOnHHZ;
660 gear^.RenderTimer:= false;
661 gear^.DirAngle:= -90 * hwSign(Gear^.dX);
662 gear^.FlightTime:= 100; // (roughly) ticks spent dropping, used to skip getting up anim when stuck.
663 // Initially set to a high value so cake has at least one getting up anim.
664 if not dX.isNegative then
665 gear^.Angle:= 1
666 else
667 gear^.Angle:= 3;
668 New(cakeData);
669 gear^.Data:= Pointer(cakeData);
670 end;
671 gtHellishBomb: begin
672 gear^.ImpactSound:= sndHellishImpact1;
673 gear^.nImpactSounds:= 4;
674 gear^.AdvBounce:= 1;
675 gear^.Radius:= 4;
676 gear^.Elasticity:= _0_5;
677 gear^.Friction:= _0_96;
678 gear^.Density:= _1_5;
679 gear^.RenderTimer:= true;
680 if gear^.Timer = 0 then gear^.Timer:= 5000
681 end;
682 gtDrill: begin
683 gear^.AdvBounce:= 1;
684 gear^.Elasticity:= _0_8;
685 gear^.Friction:= _0_8;
686 if gear^.Timer = 0 then
687 gear^.Timer:= 5000;
688 // Tag for drill strike. if 1 then first impact occured already
689 gear^.Tag := 0;
690 // Pos for state. If 1, drill is drilling
691 gear^.Pos := 0;
692 gear^.Radius:= 4;
693 gear^.Density:= _1;
694 end;
695 gtBall: begin
696 gear^.ImpactSound:= sndGrenadeImpact;
697 gear^.nImpactSounds:= 1;
698 gear^.AdvBounce:= 1;
699 gear^.Radius:= 5;
700 gear^.Tag:= random(8);
701 if gear^.Timer = 0 then gear^.Timer:= 5000;
702 gear^.Elasticity:= _0_7;
703 gear^.Friction:= _0_995;
704 gear^.Density:= _1_5;
705 end;
706 gtBallgun: begin
707 if gear^.Timer = 0 then gear^.Timer:= 5001;
708 end;
709 gtRCPlane: begin
710 if gear^.Timer = 0 then gear^.Timer:= 15000;
711 gear^.Health:= 3;
712 gear^.Radius:= 8;
713 gear^.Tint:= gear^.Hedgehog^.Team^.Clan^.Color shl 8 or $FF
714 end;
715 gtJetpack: begin
716 gear^.Health:= 2000;
717 gear^.Damage:= 100;
718 gear^.State:= Gear^.State or gstSubmersible
719 end;
720 gtMolotov: begin
721 gear^.AdvBounce:= 1;
722 gear^.Radius:= 6;
723 gear^.Elasticity:= _0_8;
724 gear^.Friction:= _0_8;
725 gear^.Density:= _2
726 end;
727 gtBirdy: begin
728 gear^.Radius:= 16; // todo: check
729 gear^.Health := 2000;
730 gear^.FlightTime := 2;
731 gear^.Z:= cCurrHHZ;
732 end;
733 gtEgg: begin
734 gear^.AdvBounce:= 1;
735 gear^.Radius:= 4;
736 gear^.Elasticity:= _0_6;
737 gear^.Friction:= _0_96;
738 gear^.Density:= _1;
739 if gear^.Timer = 0 then
740 gear^.Timer:= 3000
741 end;
742 gtPortal: begin
743 gear^.ImpactSound:= sndMelonImpact;
744 gear^.nImpactSounds:= 1;
745 gear^.Radius:= 17;
746 // set color
747 gear^.Tag:= 2 * gear^.Timer;
748 gear^.Timer:= 15000;
749 gear^.RenderTimer:= false;
750 gear^.Health:= 100;
751 gear^.Sticky:= true;
752 end;
753 gtPiano: begin
754 gear^.Radius:= 32;
755 gear^.Density:= _50;
756 end;
757 gtSineGunShot: begin
758 gear^.Radius:= 5;
759 gear^.Health:= 6000;
760 end;
761 gtFlamethrower: begin
762 gear^.Tag:= 10;
763 if gear^.Timer = 0 then gear^.Timer:= 10;
764 gear^.Health:= 500;
765 gear^.Damage:= 100;
766 end;
767 gtLandGun: begin
768 gear^.Tag:= 10;
769 if gear^.Timer = 0 then gear^.Timer:= 10;
770 gear^.Health:= 1000;
771 gear^.Damage:= 100;
772 end;
773 gtPoisonCloud: begin
774 if gear^.Timer = 0 then gear^.Timer:= 5000;
775 gear^.dY:= int2hwfloat(-4 + longint(getRandom(8))) / 1000;
776 gear^.Tint:= $C0C000C0
777 end;
778 gtResurrector: begin
779 gear^.Radius := cResurrectorDist;
780 gear^.Tag := 0;
781 gear^.Tint:= $F5DB35FF
782 end;
783 gtWaterUp: begin
784 gear^.Tag := 47;
785 end;
786 gtNapalmBomb: begin
787 gear^.Elasticity:= _0_8;
788 gear^.Friction:= _0_8;
789 if gear^.Timer = 0 then gear^.Timer:= 1000;
790 gear^.Radius:= 5;
791 gear^.Density:= _1_5;
792 end;
793 gtIceGun: begin
794 gear^.Health:= 1000;
795 gear^.Radius:= 8;
796 gear^.Density:= _0;
797 gear^.Tag:= 0; // sound state: 0 = no sound, 1 = ice beam sound, 2 = idle sound
798 end;
799 gtCreeper: begin
800 // TODO: Finish creeper initialization implementation
801 gear^.Radius:= cHHRadius;
802 gear^.Elasticity:= _0_35;
803 gear^.Friction:= _0_93;
804 gear^.Density:= _5;
805
806 gear^.AdvBounce:= 1;
807 gear^.ImpactSound:= sndAirMineImpact;
808 gear^.nImpactSounds:= 1;
809 gear^.Health:= 30;
810 gear^.Radius:= 8;
811 gear^.Angle:= 175; // Radius at which it will start "seeking". $FFFFFFFF = unlimited. check is skipped.
812 gear^.Power:= cMaxWindSpeed.QWordValue div 2; // hwFloat converted. 1/2 g default. defines the "seek" speed when a gear is in range.
813 gear^.Pos:= cMaxWindSpeed.QWordValue * 3 div 2; // air friction. slows it down when not hitting stuff
814 if gear^.Timer = 0 then
815 gear^.Timer:= 5000;
816 gear^.WDTimer:= gear^.Timer
817 end;
818 gtMinigun: begin
819 // Timer. First, it's the timer before shooting. Then it will become the shooting timer and is set to Karma
820 if gear^.Timer = 0 then
821 gear^.Timer:= 601;
822 // minigun shooting time. 1 bullet is fired every 50ms
823 gear^.Karma:= 3451;
824 end;
825 gtMinigunBullet: begin
826 gear^.Radius:= 1;
827 gear^.Health:= 2;
828 end;
829 gtGenericFaller:begin
830 gear^.AdvBounce:= 1;
831 gear^.Radius:= 1;
832 gear^.Elasticity:= _0_9;
833 gear^.Friction:= _0_995;
834 gear^.Density:= _1;
835 end;
836 end;
837
838 InsertGearToList(gear);
839 AddGear:= gear;
840
841 ScriptCall('onGearAdd', gear^.uid);
842 end;
843
844 procedure DeleteGear(Gear: PGear);
845 var team: PTeam;
846 t,i: Longword;
847 cakeData: PCakeData;
848 iterator: PGear;
849 begin
850
851 ScriptCall('onGearDelete', gear^.uid);
852
853 DeleteCI(Gear);
854 RemoveFromProximityCache(Gear);
855
856 FreeAndNilTexture(Gear^.Tex);
857
858 // remove potential links to this gear
859 // currently relevant to: gears linked by hammer
860 if (Gear^.Kind = gtHedgehog) or (Gear^.Kind = gtMine) or (Gear^.Kind = gtExplosives) then
861 begin
862 // check all gears for stuff to port through
863 iterator := nil;
864 while true do
865 begin
866
867 // iterate through GearsList
868 if iterator = nil then
869 iterator := GearsList
870 else
871 iterator := iterator^.NextGear;
872
873 // end of list?
874 if iterator = nil then
875 break;
876
877 if iterator^.LinkedGear = Gear then
878 iterator^.LinkedGear:= nil;
879 end;
880
881 end;
882
883 // make sure that portals have their link removed before deletion
884 if (Gear^.Kind = gtPortal) then
885 begin
886 if (Gear^.LinkedGear <> nil) then
887 if (Gear^.LinkedGear^.LinkedGear = Gear) then
888 Gear^.LinkedGear^.LinkedGear:= nil;
889 end
890 else if Gear^.Kind = gtCake then
891 begin
892 cakeData:= PCakeData(Gear^.Data);
893 Dispose(cakeData);
894 cakeData:= nil;
895 end
896 else if Gear^.Kind = gtHedgehog then
897 (*
898 This behaviour dates back to revision 4, and I accidentally encountered it with TARDIS. I don't think it must apply to any modern weapon, since if it was actually hit, the best the gear could do would be to destroy itself immediately, and you'd still end up with two graves. I believe it should be removed
899 if (CurAmmoGear <> nil) and (CurrentHedgehog^.Gear = Gear) then
900 begin
901 AttackBar:= 0;
902 Gear^.Message:= gmDestroy;
903 CurAmmoGear^.Message:= gmDestroy;
904 exit
905 end
906 else*)
907 begin
908 if ((CurrentHedgehog = nil) or (Gear <> CurrentHedgehog^.Gear)) or (CurAmmoGear = nil) or (CurAmmoGear^.Kind <> gtKamikaze) then
909 Gear^.Hedgehog^.Team^.Clan^.Flawless:= false;
910 if CheckCoordInWater(hwRound(Gear^.X), hwRound(Gear^.Y)) then
911 begin
912 t:= max(Gear^.Damage, Gear^.Health);
913 Gear^.Damage:= t;
914 // Display hedgehog damage in water
915 spawnHealthTagForHH(Gear, t);
916 end;
917
918 team:= Gear^.Hedgehog^.Team;
919 if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Gear = Gear) then
920 begin
921 AttackBar:= 0;
922 FreeActionsList; // to avoid ThinkThread on drawned gear
923 if ((Ammoz[CurrentHedgehog^.CurAmmoType].Ammo.Propz and ammoprop_NoRoundEnd) <> 0)
924 and (CurrentHedgehog^.MultiShootAttacks > 0) then
925 OnUsedAmmo(CurrentHedgehog^);
926 end;
927
928 Gear^.Hedgehog^.Gear:= nil;
929
930 if Gear^.Hedgehog^.King then
931 // If king died, kill the rest of the team
932 begin
933 with Gear^.Hedgehog^.Team^ do
934 begin
935 Gear^.Hedgehog^.Team^.hasKing:= false;
936 for t:= 0 to cMaxHHIndex do
937 if Hedgehogs[t].Gear <> nil then
938 Hedgehogs[t].Gear^.Health:= 0
939 else if (Hedgehogs[t].GearHidden <> nil) then
940 Hedgehogs[t].GearHidden^.Health:= 0 // Hog is still hidden. If tardis should return though, Lua, eh ...
941 end;
942 end;
943
944 // Update passive status of clan
945 if (not Gear^.Hedgehog^.Team^.Clan^.Passive) then
946 begin
947 Gear^.Hedgehog^.Team^.Clan^.Passive:= true;
948 for i:= 0 to Pred(team^.Clan^.TeamsNumber) do
949 begin
950 with team^.Clan^.Teams[i]^ do
951 if (not Passive) then
952 for t:= 0 to cMaxHHIndex do
953 if (Hedgehogs[t].Gear <> nil) or (Hedgehogs[t].GearHidden <> nil) then
954 begin
955 Gear^.Hedgehog^.Team^.Clan^.Passive:= false;
956 break;
957 end;
958 if (not Gear^.Hedgehog^.Team^.Clan^.Passive) then
959 break;
960 end;
961 end;
962
963 // should be not CurrentHedgehog, but hedgehog of the last gear which caused damage to this hog
964 // same stand for CheckHHDamage
965 if (Gear^.LastDamage <> nil) and (CurrentHedgehog <> nil) then
966 uStats.HedgehogDamaged(Gear, Gear^.LastDamage, 0, true)
967 else if CurrentHedgehog <> nil then
968 uStats.HedgehogDamaged(Gear, CurrentHedgehog, 0, true);
969
970 inc(KilledHHs);
971 RecountTeamHealth(team);
972 if (CurrentHedgehog <> nil) and (CurrentHedgehog^.Effects[heResurrectable] <> 0) and
973 //(Gear^.Hedgehog^.Effects[heResurrectable] = 0) then
974 (Gear^.Hedgehog^.Team^.Clan <> CurrentHedgehog^.Team^.Clan) then
975 with CurrentHedgehog^ do
976 begin
977 inc(Team^.stats.AIKills);
978 FreeAndNilTexture(Team^.AIKillsTex);
979 Team^.AIKillsTex := RenderStringTex(ansistring(inttostr(Team^.stats.AIKills)), Team^.Clan^.Color, fnt16);
980 end
981 end;
982 with Gear^ do
983 begin
984 AddFileLog('Delete: #' + inttostr(uid) + ' (' + inttostr(hwRound(x)) + ',' + inttostr(hwRound(y)) + '), d(' + floattostr(dX) + ',' + floattostr(dY) + ') type = ' + EnumToStr(Kind));
985 AddRandomness(X.round xor X.frac xor dX.round xor dX.frac xor Y.round xor Y.frac xor dY.round xor dY.frac)
986 end;
987 if CurAmmoGear = Gear then
988 CurAmmoGear:= nil;
989 if FollowGear = Gear then
990 FollowGear:= nil;
991 if lastGearByUID = Gear then
992 lastGearByUID := nil;
993 if (Gear^.Hedgehog = nil) or (Gear^.Hedgehog^.GearHidden <> Gear) then // hidden hedgehogs shouldn't be in the list
994 RemoveGearFromList(Gear)
995 else Gear^.Hedgehog^.GearHidden:= nil;
996
997 Dispose(Gear)
998 end;
999
1000 end.
1001