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