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
21 unit uAmmos;
22 interface
23 uses uConsts, uTypes, uStore;
24
25 procedure initModule;
26 procedure freeModule;
27
28 procedure AddAmmoStore;
29 procedure SetAmmoLoadout(var s: shortstring);
30 procedure SetAmmoProbability(var s: shortstring);
31 procedure SetAmmoDelay(var s: shortstring);
32 procedure SetAmmoReinforcement(var s: shortstring);
33 procedure AssignStores;
34 procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType);
35 procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; amt: LongWord);
36 procedure SetAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; cnt: LongWord);
HHHasAmmonull37 function HHHasAmmo(var Hedgehog: THedgehog; Ammo: TAmmoType): LongWord;
38 procedure PackAmmo(Ammo: PHHAmmo; Slot: LongInt);
39 procedure OnUsedAmmo(var Hedgehog: THedgehog);
40 procedure ApplyAngleBounds(var Hedgehog: THedgehog; AmmoType: TAmmoType);
41 procedure ApplyAmmoChanges(var Hedgehog: THedgehog);
42 procedure SwitchNotHeldAmmo(var Hedgehog: THedgehog);
43 procedure SetWeapon(weap: TAmmoType);
44 procedure DisableSomeWeapons;
45 procedure ResetWeapons;
GetAmmoByNumnull46 function GetAmmoByNum(num: LongInt): PHHAmmo;
GetCurAmmoEntrynull47 function GetCurAmmoEntry(var Hedgehog: THedgehog): PAmmo;
GetAmmoEntrynull48 function GetAmmoEntry(var Hedgehog: THedgehog; am: TAmmoType): PAmmo;
49
50 var StoreCnt: LongInt;
51
52 implementation
53 uses uVariables, uCommands, uUtils, uCaptions, uDebug, uScript;
54
55 type TAmmoArray = array[TAmmoType] of TAmmo;
56 var StoresList: array[0..Pred(cMaxHHs)] of PHHAmmo;
57 ammoLoadout, ammoProbability, ammoDelay, ammoReinforcement: shortstring;
58 InitialCountsLocal: array[0..Pred(cMaxHHs)] of TAmmoCounts;
59
60 procedure FillAmmoStore(Ammo: PHHAmmo; var newAmmo: TAmmoArray);
61 var mi: array[0..cMaxSlotIndex] of byte;
62 a: TAmmoType;
63 begin
64 {$HINTS OFF}
65 FillChar(mi, sizeof(mi), 0);
66 {$HINTS ON}
67 FillChar(Ammo^, sizeof(Ammo^), 0);
68 for a:= Low(TAmmoType) to High(TAmmoType) do
69 begin
70 if newAmmo[a].Count > 0 then
71 begin
72 if checkFails(mi[Ammoz[a].Slot] <= cMaxSlotAmmoIndex, 'Ammo slot overflow', true) then exit;
73 Ammo^[Ammoz[a].Slot, mi[Ammoz[a].Slot]]:= newAmmo[a];
74 inc(mi[Ammoz[a].Slot])
75 end
76 end;
77 AmmoMenuInvalidated:= true;
78 end;
79
80 procedure AddAmmoStore;
81 var cnt: Longword;
82 a: TAmmoType;
83 ammos: TAmmoCounts;
84 newAmmos: TAmmoArray;
85 begin
86 if checkFails((byte(ammoLoadout[0]) = byte(ord(High(TAmmoType)))) and (byte(ammoProbability[0]) = byte(ord(High(TAmmoType)))) and (byte(ammoDelay[0]) = byte(ord(High(TAmmoType)))) and (byte(ammoReinforcement[0]) = byte(ord(High(TAmmoType))))
87 , 'Incomplete or missing ammo scheme set (incompatible frontend or demo/save?)'
88 , true)
89 then exit;
90
91 if checkFails(StoreCnt < cMaxHHs, 'Ammo stores overflow', true) then exit;
92 inc(StoreCnt);
93
94 new(StoresList[Pred(StoreCnt)]);
95
96 for a:= Low(TAmmoType) to High(TAmmoType) do
97 begin
98 if a <> amNothing then
99 begin
100 Ammoz[a].Probability:= probabilityLevels[byte(ammoProbability[ord(a)]) - byte('0')];
101 Ammoz[a].SkipTurns:= (byte(ammoDelay[ord(a)]) - byte('0'));
102 Ammoz[a].NumberInCase:= (byte(ammoReinforcement[ord(a)]) - byte('0'));
103 cnt:= byte(ammoLoadout[ord(a)]) - byte('0');
104 // avoid things we already have infinite number
105 if cnt = 9 then
106 begin
107 cnt:= AMMO_INFINITE;
108 Ammoz[a].Probability:= 0
109 end;
110 if Ammoz[a].NumberInCase = 0 then
111 Ammoz[a].Probability:= 0;
112
113 // avoid things we already have by scheme
114 // merge this into DisableSomeWeapons ?
115 if ((a = amLowGravity) and ((GameFlags and gfLowGravity) <> 0))
116 or ((a = amInvulnerable) and ((GameFlags and gfInvulnerable) <> 0))
117 or ((a = amLaserSight) and ((GameFlags and gfLaserSight) <> 0))
118 or ((a = amVampiric) and ((GameFlags and gfVampiric) <> 0))
119 or ((a = amExtraTime) and (cHedgehogTurnTime >= 1000000))
120 // Always remove creeper because it's unfinished.
121 // TODO: Re-enable creeper when creeper is done
122 or (a = amCreeper) then
123 begin
124 cnt:= 0;
125 Ammoz[a].Probability:= 0
126 end;
127 ammos[a]:= cnt;
128
129 if (((GameFlags and gfPlaceHog) <> 0)
130 or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
131 and (a <> amTeleport) and (a <> amSkip)
132 and (Ammoz[a].SkipTurns < 10000) then
133 inc(Ammoz[a].SkipTurns,10000);
134 if (((GameFlags and gfPlaceHog) <> 0)
135 or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
136 and (a = amTeleport) then
137 ammos[a]:= AMMO_INFINITE
138 end
139
140 else
141 ammos[a]:= AMMO_INFINITE;
142
143 if (((GameFlags and gfPlaceHog) <> 0)
144 or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
145 and (a = amTeleport) then
146 begin
147 InitialCountsLocal[Pred(StoreCnt)][a]:= cnt;
148 InitialAmmoCounts[a]:= cnt;
149 end
150 else
151 begin
152 InitialCountsLocal[Pred(StoreCnt)][a]:= ammos[a];
153 InitialAmmoCounts[a]:= ammos[a];
154 end
155 end;
156
157 for a:= Low(TAmmoType) to High(TAmmoType) do
158 begin
159 newAmmos[a]:= Ammoz[a].Ammo;
160 newAmmos[a].Count:= ammos[a]
161 end;
162
163 FillAmmoStore(StoresList[Pred(StoreCnt)], newAmmos)
164 end;
165
166 function GetAmmoByNum(num: LongInt): PHHAmmo;
167 begin
168 if checkFails(num < StoreCnt, 'Invalid ammo store number', true) then
169 GetAmmoByNum:= nil
170 else
171 GetAmmoByNum:= StoresList[num]
172 end;
173
174 function GetCurAmmoEntry(var Hedgehog: THedgehog): PAmmo;
175 begin
176 GetCurAmmoEntry:= GetAmmoEntry(Hedgehog, Hedgehog.CurAmmoType)
177 end;
178
179 function GetAmmoEntry(var Hedgehog: THedgehog; am: TAmmoType): PAmmo;
180 var ammoidx, slot: LongWord;
181 begin
182 with Hedgehog do
183 begin
184 slot:= Ammoz[am].Slot;
185 ammoidx:= 0;
186 while (ammoidx < cMaxSlotAmmoIndex) and (Ammo^[slot, ammoidx].AmmoType <> am) do
187 inc(ammoidx);
188 GetAmmoEntry:= @Ammo^[slot, ammoidx];
189 if (Ammo^[slot, ammoidx].AmmoType <> am) then
190 GetAmmoEntry:= GetAmmoEntry(Hedgehog, amNothing)
191 end;
192 end;
193
194 procedure AssignStores;
195 var t: LongInt;
196 i: Longword;
197 begin
198 for t:= 0 to Pred(TeamsCount) do
199 with TeamsArray[t]^ do
200 begin
201 for i:= 0 to cMaxHHIndex do
202 if Hedgehogs[i].Gear <> nil then
203 begin
204 Hedgehogs[i].Ammo:= GetAmmoByNum(Hedgehogs[i].AmmoStore);
205 if ((GameFlags and gfPlaceHog) <> 0) or (((GameFlags and gfKing) <> 0) and (Hedgehogs[i].King = true)) then
206 Hedgehogs[i].CurAmmoType:= amTeleport
207 else
208 Hedgehogs[i].CurAmmoType:= amNothing
209 end
210 end
211 end;
212
213 procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; amt: LongWord);
214 var cnt: LongWord;
215 a: PAmmo;
216 begin
217 a:= GetAmmoEntry(Hedgehog, ammo);
218 if (a^.AmmoType <> amNothing) then
219 cnt:= a^.Count
220 else
221 cnt:= 0;
222 if (cnt >= AMMO_INFINITE) or (amt >= AMMO_INFINITE) then
223 cnt:= AMMO_INFINITE
224 else
225 cnt:= min(AMMO_FINITE_MAX, cnt + amt);
226 SetAmmo(Hedgehog, ammo, cnt);
227 end;
228
229 procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType);
230 begin
231 AddAmmo(Hedgehog, ammo, Ammoz[ammo].NumberInCase);
232 end;
233
234 procedure SetAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; cnt: LongWord);
235 var ammos: TAmmoArray;
236 slot, ami: LongInt;
237 hhammo: PHHAmmo;
238 CurWeapon: PAmmo;
239 a: TAmmoType;
240 begin
241 if ammo = amNothing then exit;
242 {$HINTS OFF}
243 FillChar(ammos, sizeof(ammos), 0);
244 {$HINTS ON}
245 hhammo:= Hedgehog.Ammo;
246
247 for a:= Low(TAmmoType) to High(TAmmoType) do
248 begin
249 ammos[a]:= Ammoz[a].Ammo;
250 ammos[a].Count:= 0
251 end;
252
253 for slot:= 0 to cMaxSlotIndex do
254 for ami:= 0 to cMaxSlotAmmoIndex do
255 if hhammo^[slot, ami].Count > 0 then
256 ammos[hhammo^[slot, ami].AmmoType]:= hhammo^[slot, ami];
257
258 ammos[ammo].Count:= cnt;
259 if ammos[ammo].Count > AMMO_INFINITE then ammos[ammo].Count:= AMMO_INFINITE;
260
261 FillAmmoStore(hhammo, ammos);
262 CurWeapon:= GetCurAmmoEntry(Hedgehog);
263 with Hedgehog, CurWeapon^ do
264 if (Count = 0) or (AmmoType = amNothing) then
265 begin
266 PackAmmo(Ammo, Ammoz[AmmoType].Slot);
267 CurAmmoType:= amNothing
268 end;
269 if Hedgehog.BotLevel <> 0 then
270 Hedgehog.Gear^.AIHints := Hedgehog.Gear^.AIHints or aihAmmosChanged;
271 end;
272
273 procedure PackAmmo(Ammo: PHHAmmo; Slot: LongInt);
274 var ami: LongInt;
275 b: boolean;
276 begin
277 repeat
278 b:= false;
279 ami:= 0;
280 while (not b) and (ami < cMaxSlotAmmoIndex) do
281 if (Ammo^[Slot, ami].Count = 0)
282 and (Ammo^[Slot, ami + 1].Count > 0) then
283 b:= true
284 else
285 inc(ami);
286 if b then // there is a free item in ammo stack
287 begin
288 Ammo^[Slot, ami]:= Ammo^[Slot, ami + 1];
289 Ammo^[Slot, ami + 1].Count:= 0
290 end;
291 until (not b);
292 AmmoMenuInvalidated:= true;
293 end;
294
295 procedure OnUsedAmmo(var Hedgehog: THedgehog);
296 var CurWeapon: PAmmo;
297 begin
298 CurWeapon:= GetCurAmmoEntry(Hedgehog);
299 with Hedgehog do
300 begin
301 if CurAmmoType <> amNothing then
302 ScriptCall('onUsedAmmo', ord(CurAmmoType));
303
304 MultiShootAttacks:= 0;
305 with CurWeapon^ do
306 if Count <> AMMO_INFINITE then
307 begin
308 dec(Count);
309 if Count = 0 then
310 begin
311 PackAmmo(Ammo, Ammoz[AmmoType].Slot);
312 //SwitchNotHeldAmmo(Hedgehog);
313 if CurAmmoType = amKnife then LoadHedgehogHat(Hedgehog, Hedgehog.Hat);
314 CurAmmoType:= amNothing
315 end
316 end
317 end;
318 end;
319
320 function HHHasAmmo(var Hedgehog: THedgehog; Ammo: TAmmoType): LongWord;
321 var slot, ami: LongInt;
322 begin
323 HHHasAmmo:= 0;
324 Slot:= Ammoz[Ammo].Slot;
325 ami:= 0;
326 while (ami <= cMaxSlotAmmoIndex) do
327 begin
328 with Hedgehog.Ammo^[Slot, ami] do
329 if (AmmoType = Ammo) then
330 if Hedgehog.Team^.Clan^.TurnNumber > Ammoz[AmmoType].SkipTurns then
331 exit(Count)
332 else
333 exit(0);
334 inc(ami)
335 end;
336 end;
337
338 procedure ApplyAngleBounds(var Hedgehog: THedgehog; AmmoType: TAmmoType);
339 begin
340 if Hedgehog.Gear <> nil then
341 with Hedgehog do
342 begin
343 if (AmmoType <> amNothing) then
344 begin
345 if ((CurAmmoGear <> nil) and (CurAmmoGear^.AmmoType = amRope)) then
346 begin
347 CurMaxAngle:= Ammoz[amRope].maxAngle;
348 CurMinAngle:= Ammoz[amRope].minAngle;
349 end
350 else
351 begin
352 CurMinAngle:= Ammoz[AmmoType].minAngle;
353 if Ammoz[AmmoType].maxAngle <> 0 then
354 CurMaxAngle:= Ammoz[AmmoType].maxAngle
355 else
356 CurMaxAngle:= cMaxAngle;
357 end;
358
359 with Hedgehog.Gear^ do
360 begin
361 if Angle < CurMinAngle then
362 Angle:= CurMinAngle;
363 if Angle > CurMaxAngle then
364 Angle:= CurMaxAngle;
365 end
366 end
367 end
368 end;
369
370 procedure SwitchToFirstLegalAmmo(var Hedgehog: THedgehog);
371 var slot, ammoidx: LongWord;
372 begin
373 with Hedgehog do
374 begin
375 CurAmmoType:= amNothing;
376 slot:= 0;
377 ammoidx:= 0;
378 while (slot <= cMaxSlotIndex) and
379 ((Ammo^[slot, ammoidx].Count = 0) or
380 (Ammoz[Ammo^[slot, ammoidx].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0))
381 do
382 begin
383 while (ammoidx <= cMaxSlotAmmoIndex)
384 and ((Ammo^[slot, ammoidx].Count = 0) or (Ammoz[Ammo^[slot, ammoidx].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0))
385 do inc(ammoidx);
386
387 if (ammoidx > cMaxSlotAmmoIndex) then
388 begin
389 ammoidx:= 0;
390 inc(slot)
391 end
392 end;
393 if checkFails(slot <= cMaxSlotIndex, 'Ammo slot index overflow', true) then exit;
394 CurAmmoType:= Ammo^[slot, ammoidx].AmmoType;
395 end
396 end;
397
398 procedure ApplyAmmoChanges(var Hedgehog: THedgehog);
399 var s: ansistring;
400 OldWeapon, CurWeapon: PAmmo;
401 begin
402 TargetPoint.X:= NoPointX;
403
404 with Hedgehog do
405 begin
406 CurWeapon:= GetCurAmmoEntry(Hedgehog);
407 OldWeapon:= GetCurAmmoEntry(Hedgehog);
408
409 if (Hedgehog.Gear^.State and gstHHDriven) = 0 then
410 Hedgehog.CurAmmoType:= amNothing
411 else if (CurWeapon^.Count = 0) then
412 SwitchToFirstLegalAmmo(Hedgehog)
413 else if CurWeapon^.AmmoType = amNothing then
414 Hedgehog.CurAmmoType:= amNothing;
415
416 CurWeapon:= GetCurAmmoEntry(Hedgehog);
417
418 // Weapon selection animation (if new ammo type)
419 if CurWeapon^.AmmoType <> OldWeapon^.AmmoType then
420 Timer:= 10;
421
422 ApplyAngleBounds(Hedgehog, CurWeapon^.AmmoType);
423
424 with CurWeapon^ do
425 begin
426 if length(trluaammo[Ammoz[AmmoType].NameId]) > 0 then
427 s:= trluaammo[Ammoz[AmmoType].NameId]
428 else
429 s:= trammo[Ammoz[AmmoType].NameId];
430 if (Count <> AMMO_INFINITE) and (not (Hedgehog.Team^.ExtDriven or (Hedgehog.BotLevel > 0))) then
431 s:= s + ansistring(' (' + IntToStr(Count) + ')');
432 if (Propz and ammoprop_Timerable) <> 0 then
433 s:= s + ansistring(', ' + IntToStr(Timer div 1000) + ' ') + trammo[sidSeconds];
434 if (Hedgehog.Gear^.State and gstHHDriven) <> 0 then
435 AddCaption(s, Team^.Clan^.Color, capgrpAmmoinfo);
436 if (Propz and ammoprop_NeedTarget) <> 0 then
437 begin
438 if Gear <> nil then Gear^.State:= Gear^.State or gstChooseTarget;
439 isCursorVisible:= true
440 end
441 else
442 begin
443 if Gear <> nil then Gear^.State:= Gear^.State and (not gstChooseTarget);
444 isCursorVisible:= false
445 end;
446 end
447 end;
448 end;
449
450 procedure SwitchNotHeldAmmo(var Hedgehog: THedgehog);
451 begin
452 with Hedgehog do
453 if ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_DontHold) <> 0)
454 or (Ammoz[CurAmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0) then
455 SwitchToFirstLegalAmmo(Hedgehog);
456 end;
457
458 procedure SetWeapon(weap: TAmmoType);
459 begin
460 ParseCommand('/setweap ' + char(weap), true)
461 end;
462
463 procedure DisableSomeWeapons;
464 var i, slot, a: Longword;
465 t: TAmmoType;
466 begin
467 for i:= 0 to Pred(StoreCnt) do
468 for slot:= 0 to cMaxSlotIndex do
469 begin
470 for a:= 0 to cMaxSlotAmmoIndex do
471 with StoresList[i]^[slot, a] do
472 if (Propz and ammoprop_NotBorder) <> 0 then
473 begin
474 Count:= 0;
475 InitialCountsLocal[i][AmmoType]:= 0
476 end;
477
478 PackAmmo(StoresList[i], slot)
479 end;
480
481 for t:= Low(TAmmoType) to High(TAmmoType) do
482 if (Ammoz[t].Ammo.Propz and ammoprop_NotBorder) <> 0 then
483 Ammoz[t].Probability:= 0
484 end;
485
486 procedure SetAmmoLoadout(var s: shortstring);
487 begin
488 ammoLoadout:= s;
489 end;
490
491 procedure SetAmmoProbability(var s: shortstring);
492 begin
493 ammoProbability:= s;
494 end;
495
496 procedure SetAmmoDelay(var s: shortstring);
497 begin
498 ammoDelay:= s;
499 end;
500
501 procedure SetAmmoReinforcement(var s: shortstring);
502 begin
503 ammoReinforcement:= s;
504 end;
505
506 // Restore indefinitely disabled weapons and initial weapon counts.
507 procedure ResetWeapons;
508 var i, t: Longword;
509 a: TAmmoType;
510 newAmmos: TAmmoArray;
511 begin
512 for t:= 0 to Pred(TeamsCount) do
513 with TeamsArray[t]^ do
514 for i:= 0 to cMaxHHIndex do
515 Hedgehogs[i].CurAmmoType:= amNothing;
516
517 for a:= Low(TAmmoType) to High(TAmmoType) do
518 newAmmos[a]:= Ammoz[a].Ammo;
519
520 for i:= 0 to Pred(StoreCnt) do
521 begin
522 for a:= Low(TAmmoType) to High(TAmmoType) do
523 newAmmos[a].Count:= InitialCountsLocal[i][a];
524 FillAmmoStore(StoresList[i], newAmmos);
525 end;
526
527 for a:= Low(TAmmoType) to High(TAmmoType) do
528 if Ammoz[a].SkipTurns >= 10000 then
529 dec(Ammoz[a].SkipTurns,10000)
530 end;
531
532
533
534 procedure chAddAmmoStore(var descr: shortstring);
535 begin
536 descr:= ''; // avoid compiler hint
537 AddAmmoStore
538 end;
539
540 procedure initModule;
541 var i: Longword;
542 begin
543 RegisterVariable('ammloadt', @SetAmmoLoadout, false);
544 RegisterVariable('ammdelay', @SetAmmoDelay, false);
545 RegisterVariable('ammprob', @SetAmmoProbability, false);
546 RegisterVariable('ammreinf', @SetAmmoReinforcement, false);
547 RegisterVariable('ammstore', @chAddAmmoStore , false);
548
549 CurMinAngle:= 0;
550 CurMaxAngle:= cMaxAngle;
551 StoreCnt:= 0;
552 ammoLoadout:= '';
553 ammoProbability:= '';
554 ammoDelay:= '';
555 ammoReinforcement:= '';
556 for i:=1 to ord(High(TAmmoType)) do
557 begin
558 ammoLoadout:= ammoLoadout + '0';
559 ammoProbability:= ammoProbability + '0';
560 ammoDelay:= ammoDelay + '0';
561 ammoReinforcement:= ammoReinforcement + '0'
562 end;
563 FillChar(InitialCountsLocal, sizeof(InitialCountsLocal), 0)
564 end;
565
566 procedure freeModule;
567 var i: LongWord;
568 begin
569 if StoreCnt > 0 then
570 for i:= 0 to Pred(StoreCnt) do
571 Dispose(StoresList[i])
572 end;
573
574 end.
575