(*
 * Hedgewars, a free turn based strategy game
 * Copyright (c) 2004-2015 Andrey Korotaev <unC0Rr@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *)

{$INCLUDE "options.inc"}

unit uAmmos;
interface
uses uConsts, uTypes, uStore;

procedure initModule;
procedure freeModule;

procedure AddAmmoStore;
procedure SetAmmoLoadout(var s: shortstring);
procedure SetAmmoProbability(var s: shortstring);
procedure SetAmmoDelay(var s: shortstring);
procedure SetAmmoReinforcement(var s: shortstring);
procedure AssignStores;
procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType);
procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; amt: LongWord);
procedure SetAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; cnt: LongWord);
function  HHHasAmmo(var Hedgehog: THedgehog; Ammo: TAmmoType): LongWord;
procedure PackAmmo(Ammo: PHHAmmo; Slot: LongInt);
procedure OnUsedAmmo(var Hedgehog: THedgehog);
procedure ApplyAngleBounds(var Hedgehog: THedgehog; AmmoType: TAmmoType);
procedure ApplyAmmoChanges(var Hedgehog: THedgehog);
procedure SwitchNotHeldAmmo(var Hedgehog: THedgehog);
procedure SetWeapon(weap: TAmmoType);
procedure DisableSomeWeapons;
procedure ResetWeapons;
function  GetAmmoByNum(num: LongInt): PHHAmmo;
function  GetCurAmmoEntry(var Hedgehog: THedgehog): PAmmo;
function  GetAmmoEntry(var Hedgehog: THedgehog; am: TAmmoType): PAmmo;

var StoreCnt: LongInt;

implementation
uses uVariables, uCommands, uUtils, uCaptions, uDebug, uScript;

type TAmmoArray = array[TAmmoType] of TAmmo;
var StoresList: array[0..Pred(cMaxHHs)] of PHHAmmo;
    ammoLoadout, ammoProbability, ammoDelay, ammoReinforcement: shortstring;
    InitialCountsLocal: array[0..Pred(cMaxHHs)] of TAmmoCounts;

procedure FillAmmoStore(Ammo: PHHAmmo; var newAmmo: TAmmoArray);
var mi: array[0..cMaxSlotIndex] of byte;
    a: TAmmoType;
begin
{$HINTS OFF}
FillChar(mi, sizeof(mi), 0);
{$HINTS ON}
FillChar(Ammo^, sizeof(Ammo^), 0);
for a:= Low(TAmmoType) to High(TAmmoType) do
    begin
    if newAmmo[a].Count > 0 then
        begin
        if checkFails(mi[Ammoz[a].Slot] <= cMaxSlotAmmoIndex, 'Ammo slot overflow', true) then exit;
        Ammo^[Ammoz[a].Slot, mi[Ammoz[a].Slot]]:= newAmmo[a];
        inc(mi[Ammoz[a].Slot])
        end
    end;
AmmoMenuInvalidated:= true;
end;

procedure AddAmmoStore;
var cnt: Longword;
    a: TAmmoType;
    ammos: TAmmoCounts;
    newAmmos: TAmmoArray;
begin
    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))))
                  , 'Incomplete or missing ammo scheme set (incompatible frontend or demo/save?)'
                  , true)
    then exit;

if checkFails(StoreCnt < cMaxHHs, 'Ammo stores overflow', true) then exit;
inc(StoreCnt);

new(StoresList[Pred(StoreCnt)]);

for a:= Low(TAmmoType) to High(TAmmoType) do
    begin
    if a <> amNothing then
        begin
        Ammoz[a].Probability:= probabilityLevels[byte(ammoProbability[ord(a)]) - byte('0')];
        Ammoz[a].SkipTurns:= (byte(ammoDelay[ord(a)]) - byte('0'));
        Ammoz[a].NumberInCase:= (byte(ammoReinforcement[ord(a)]) - byte('0'));
        cnt:= byte(ammoLoadout[ord(a)]) - byte('0');
        // avoid things we already have infinite number
        if cnt = 9 then
            begin
            cnt:= AMMO_INFINITE;
            Ammoz[a].Probability:= 0
            end;
        if Ammoz[a].NumberInCase = 0 then
            Ammoz[a].Probability:= 0;

        // avoid things we already have by scheme
        // merge this into DisableSomeWeapons ?
        if ((a = amLowGravity) and ((GameFlags and gfLowGravity) <> 0))
        or ((a = amInvulnerable) and ((GameFlags and gfInvulnerable) <> 0))
        or ((a = amLaserSight) and ((GameFlags and gfLaserSight) <> 0))
        or ((a = amVampiric) and ((GameFlags and gfVampiric) <> 0))
        or ((a = amExtraTime) and (cHedgehogTurnTime >= 1000000))
        // Always remove creeper because it's unfinished.
        // TODO: Re-enable creeper when creeper is done
        or (a = amCreeper) then
            begin
            cnt:= 0;
            Ammoz[a].Probability:= 0
            end;
        ammos[a]:= cnt;

        if (((GameFlags and gfPlaceHog) <> 0)
        or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
        and (a <> amTeleport) and (a <> amSkip)
        and (Ammoz[a].SkipTurns < 10000) then
            inc(Ammoz[a].SkipTurns,10000);
    if (((GameFlags and gfPlaceHog) <> 0)
    or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
    and (a = amTeleport) then
        ammos[a]:= AMMO_INFINITE
        end

    else
        ammos[a]:= AMMO_INFINITE;

    if (((GameFlags and gfPlaceHog) <> 0)
    or (((GameFlags and gfKing) <> 0) and ((GameFlags and gfPlaceHog) = 0)))
    and (a = amTeleport) then
        begin
        InitialCountsLocal[Pred(StoreCnt)][a]:= cnt;
        InitialAmmoCounts[a]:= cnt;
        end
    else
        begin
        InitialCountsLocal[Pred(StoreCnt)][a]:= ammos[a];
        InitialAmmoCounts[a]:= ammos[a];
        end
    end;

    for a:= Low(TAmmoType) to High(TAmmoType) do
        begin
        newAmmos[a]:= Ammoz[a].Ammo;
        newAmmos[a].Count:= ammos[a]
        end;

FillAmmoStore(StoresList[Pred(StoreCnt)], newAmmos)
end;

function GetAmmoByNum(num: LongInt): PHHAmmo;
begin
    if checkFails(num < StoreCnt, 'Invalid ammo store number', true) then
        GetAmmoByNum:= nil
    else
        GetAmmoByNum:= StoresList[num]
end;

function GetCurAmmoEntry(var Hedgehog: THedgehog): PAmmo;
begin
    GetCurAmmoEntry:= GetAmmoEntry(Hedgehog, Hedgehog.CurAmmoType)
end;

function GetAmmoEntry(var Hedgehog: THedgehog; am: TAmmoType): PAmmo;
var ammoidx, slot: LongWord;
begin
with Hedgehog do
    begin
    slot:= Ammoz[am].Slot;
    ammoidx:= 0;
    while (ammoidx < cMaxSlotAmmoIndex) and (Ammo^[slot, ammoidx].AmmoType <> am) do
        inc(ammoidx);
    GetAmmoEntry:= @Ammo^[slot, ammoidx];
    if (Ammo^[slot, ammoidx].AmmoType <> am) then
        GetAmmoEntry:= GetAmmoEntry(Hedgehog, amNothing)
    end;
end;

procedure AssignStores;
var t: LongInt;
    i: Longword;
begin
for t:= 0 to Pred(TeamsCount) do
    with TeamsArray[t]^ do
        begin
        for i:= 0 to cMaxHHIndex do
            if Hedgehogs[i].Gear <> nil then
                begin
                Hedgehogs[i].Ammo:= GetAmmoByNum(Hedgehogs[i].AmmoStore);
                if ((GameFlags and gfPlaceHog) <> 0) or (((GameFlags and gfKing) <> 0) and (Hedgehogs[i].King = true)) then
                    Hedgehogs[i].CurAmmoType:= amTeleport
                else
                    Hedgehogs[i].CurAmmoType:= amNothing
                end
        end
end;

procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; amt: LongWord);
var cnt: LongWord;
    a: PAmmo;
begin
a:= GetAmmoEntry(Hedgehog, ammo);
if (a^.AmmoType <> amNothing) then
    cnt:= a^.Count
else
    cnt:= 0;
if (cnt >= AMMO_INFINITE) or (amt >= AMMO_INFINITE) then
    cnt:= AMMO_INFINITE
else
    cnt:= min(AMMO_FINITE_MAX, cnt + amt);
SetAmmo(Hedgehog, ammo, cnt);
end;

procedure AddAmmo(var Hedgehog: THedgehog; ammo: TAmmoType);
begin
    AddAmmo(Hedgehog, ammo, Ammoz[ammo].NumberInCase);
end;

procedure SetAmmo(var Hedgehog: THedgehog; ammo: TAmmoType; cnt: LongWord);
var ammos: TAmmoArray;
    slot, ami: LongInt;
    hhammo: PHHAmmo;
    CurWeapon: PAmmo;
    a: TAmmoType;
begin
if ammo = amNothing then exit;
{$HINTS OFF}
FillChar(ammos, sizeof(ammos), 0);
{$HINTS ON}
hhammo:= Hedgehog.Ammo;

for a:= Low(TAmmoType) to High(TAmmoType) do
    begin
    ammos[a]:= Ammoz[a].Ammo;
    ammos[a].Count:= 0
    end;

for slot:= 0 to cMaxSlotIndex do
    for ami:= 0 to cMaxSlotAmmoIndex do
        if hhammo^[slot, ami].Count > 0 then
            ammos[hhammo^[slot, ami].AmmoType]:= hhammo^[slot, ami];

ammos[ammo].Count:= cnt;
if ammos[ammo].Count > AMMO_INFINITE then ammos[ammo].Count:= AMMO_INFINITE;

FillAmmoStore(hhammo, ammos);
CurWeapon:= GetCurAmmoEntry(Hedgehog);
with Hedgehog, CurWeapon^ do
    if (Count = 0) or (AmmoType = amNothing) then
        begin
        PackAmmo(Ammo, Ammoz[AmmoType].Slot);
        CurAmmoType:= amNothing
        end;
if Hedgehog.BotLevel <> 0 then
    Hedgehog.Gear^.AIHints := Hedgehog.Gear^.AIHints or aihAmmosChanged;
end;

procedure PackAmmo(Ammo: PHHAmmo; Slot: LongInt);
var ami: LongInt;
    b: boolean;
begin
    repeat
        b:= false;
        ami:= 0;
        while (not b) and (ami < cMaxSlotAmmoIndex) do
            if (Ammo^[Slot, ami].Count = 0)
            and (Ammo^[Slot, ami + 1].Count > 0) then
                b:= true
            else
                inc(ami);
        if b then // there is a free item in ammo stack
            begin
            Ammo^[Slot, ami]:= Ammo^[Slot, ami + 1];
            Ammo^[Slot, ami + 1].Count:= 0
            end;
    until (not b);
AmmoMenuInvalidated:= true;
end;

procedure OnUsedAmmo(var Hedgehog: THedgehog);
var CurWeapon: PAmmo;
begin
CurWeapon:= GetCurAmmoEntry(Hedgehog);
with Hedgehog do
    begin
    if CurAmmoType <> amNothing then
        ScriptCall('onUsedAmmo', ord(CurAmmoType));

    MultiShootAttacks:= 0;
    with CurWeapon^ do
        if Count <> AMMO_INFINITE then
            begin
            dec(Count);
            if Count = 0 then
                begin
                PackAmmo(Ammo, Ammoz[AmmoType].Slot);
                //SwitchNotHeldAmmo(Hedgehog);
                if CurAmmoType = amKnife then LoadHedgehogHat(Hedgehog, Hedgehog.Hat);
                CurAmmoType:= amNothing
                end
            end
    end;
end;

function  HHHasAmmo(var Hedgehog: THedgehog; Ammo: TAmmoType): LongWord;
var slot, ami: LongInt;
begin
    HHHasAmmo:= 0;
    Slot:= Ammoz[Ammo].Slot;
    ami:= 0;
    while (ami <= cMaxSlotAmmoIndex) do
    begin
        with Hedgehog.Ammo^[Slot, ami] do
            if (AmmoType = Ammo) then
                if Hedgehog.Team^.Clan^.TurnNumber > Ammoz[AmmoType].SkipTurns then
                    exit(Count)
                else
                    exit(0);
        inc(ami)
    end;
end;

procedure ApplyAngleBounds(var Hedgehog: THedgehog; AmmoType: TAmmoType);
begin
if Hedgehog.Gear <> nil then
    with Hedgehog do
        begin
        if (AmmoType <> amNothing) then
            begin
            if ((CurAmmoGear <> nil) and (CurAmmoGear^.AmmoType = amRope)) then
                begin
                CurMaxAngle:= Ammoz[amRope].maxAngle;
                CurMinAngle:= Ammoz[amRope].minAngle;
                end
            else
                begin
                CurMinAngle:= Ammoz[AmmoType].minAngle;
                if Ammoz[AmmoType].maxAngle <> 0 then
                    CurMaxAngle:= Ammoz[AmmoType].maxAngle
                else
                    CurMaxAngle:= cMaxAngle;
                end;

            with Hedgehog.Gear^ do
                begin
                if Angle < CurMinAngle then
                    Angle:= CurMinAngle;
                if Angle > CurMaxAngle then
                    Angle:= CurMaxAngle;
                end
            end
        end
end;

procedure SwitchToFirstLegalAmmo(var Hedgehog: THedgehog);
var slot, ammoidx: LongWord;
begin
with Hedgehog do
    begin
    CurAmmoType:= amNothing;
    slot:= 0;
    ammoidx:= 0;
    while (slot <= cMaxSlotIndex) and
        ((Ammo^[slot, ammoidx].Count = 0) or
        (Ammoz[Ammo^[slot, ammoidx].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0))
        do
            begin
            while (ammoidx <= cMaxSlotAmmoIndex)
            and ((Ammo^[slot, ammoidx].Count = 0) or (Ammoz[Ammo^[slot, ammoidx].AmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0))
                do inc(ammoidx);

        if (ammoidx > cMaxSlotAmmoIndex) then
            begin
            ammoidx:= 0;
            inc(slot)
            end
        end;
    if checkFails(slot <= cMaxSlotIndex, 'Ammo slot index overflow', true) then exit;
    CurAmmoType:= Ammo^[slot, ammoidx].AmmoType;
    end
end;

procedure ApplyAmmoChanges(var Hedgehog: THedgehog);
var s: ansistring;
    OldWeapon, CurWeapon: PAmmo;
begin
TargetPoint.X:= NoPointX;

with Hedgehog do
    begin
    CurWeapon:= GetCurAmmoEntry(Hedgehog);
    OldWeapon:= GetCurAmmoEntry(Hedgehog);

    if (Hedgehog.Gear <> nil) and (Hedgehog.Gear^.State and gstHHDriven = 0) then
        Hedgehog.CurAmmoType:= amNothing
    else if (CurWeapon^.Count = 0) then
        SwitchToFirstLegalAmmo(Hedgehog)
    else if CurWeapon^.AmmoType = amNothing then
        Hedgehog.CurAmmoType:= amNothing;

    CurWeapon:= GetCurAmmoEntry(Hedgehog);

    // Weapon selection animation (if new ammo type)
    if CurWeapon^.AmmoType <> OldWeapon^.AmmoType then
        Timer:= 10;

    ApplyAngleBounds(Hedgehog, CurWeapon^.AmmoType);

    with CurWeapon^ do
        begin
        if length(trluaammo[Ammoz[AmmoType].NameId]) > 0 then
            s:= trluaammo[Ammoz[AmmoType].NameId]
        else
            s:= trammo[Ammoz[AmmoType].NameId];
        if (Count <> AMMO_INFINITE) and (not (Hedgehog.Team^.ExtDriven or (Hedgehog.BotLevel > 0))) then
            s:= s + ansistring(' (' + IntToStr(Count) + ')');
        if (Propz and ammoprop_Timerable) <> 0 then
            s:= s + ansistring(', ' + IntToStr(Timer div 1000) + ' ') + trammo[sidSeconds];
        if (Hedgehog.Gear <> nil) and (Hedgehog.Gear^.State and gstHHDriven <> 0) then
            AddCaption(s, Team^.Clan^.Color, capgrpAmmoinfo);
        if (Propz and ammoprop_NeedTarget) <> 0 then
            begin
            if Gear <> nil then Gear^.State:= Gear^.State or      gstChooseTarget;
            isCursorVisible:= true
            end
        else
            begin
            if Gear <> nil then Gear^.State:= Gear^.State and (not gstChooseTarget);
            isCursorVisible:= false
            end;
        end
    end;
end;

procedure SwitchNotHeldAmmo(var Hedgehog: THedgehog);
begin
with Hedgehog do
    if ((Ammoz[CurAmmoType].Ammo.Propz and ammoprop_DontHold) <> 0)
    or (Ammoz[CurAmmoType].SkipTurns - CurrentTeam^.Clan^.TurnNumber >= 0) then
        SwitchToFirstLegalAmmo(Hedgehog);
end;

procedure SetWeapon(weap: TAmmoType);
begin
ParseCommand('/setweap ' + char(weap), true)
end;

procedure DisableSomeWeapons;
var i, slot, a: Longword;
    t: TAmmoType;
begin
for i:= 0 to Pred(StoreCnt) do
    for slot:= 0 to cMaxSlotIndex do
        begin
        for a:= 0 to cMaxSlotAmmoIndex do
            with StoresList[i]^[slot, a] do
                if (Propz and ammoprop_NotBorder) <> 0 then
                    begin
                    Count:= 0;
                    InitialCountsLocal[i][AmmoType]:= 0
                    end;

        PackAmmo(StoresList[i], slot)
        end;

for t:= Low(TAmmoType) to High(TAmmoType) do
    if (Ammoz[t].Ammo.Propz and ammoprop_NotBorder) <> 0 then
        Ammoz[t].Probability:= 0
end;

procedure SetAmmoLoadout(var s: shortstring);
begin
    ammoLoadout:= s;
end;

procedure SetAmmoProbability(var s: shortstring);
begin
    ammoProbability:= s;
end;

procedure SetAmmoDelay(var s: shortstring);
begin
    ammoDelay:= s;
end;

procedure SetAmmoReinforcement(var s: shortstring);
begin
    ammoReinforcement:= s;
end;

// Restore indefinitely disabled weapons and initial weapon counts.
procedure ResetWeapons;
var i, t: Longword;
    a: TAmmoType;
    newAmmos: TAmmoArray;
begin
for t:= 0 to Pred(TeamsCount) do
    with TeamsArray[t]^ do
        for i:= 0 to cMaxHHIndex do
            Hedgehogs[i].CurAmmoType:= amNothing;

for a:= Low(TAmmoType) to High(TAmmoType) do
    newAmmos[a]:= Ammoz[a].Ammo;

for i:= 0 to Pred(StoreCnt) do
    begin
    for a:= Low(TAmmoType) to High(TAmmoType) do
        newAmmos[a].Count:= InitialCountsLocal[i][a];
    FillAmmoStore(StoresList[i], newAmmos);
    end;

for a:= Low(TAmmoType) to High(TAmmoType) do
    if Ammoz[a].SkipTurns >= 10000 then
        dec(Ammoz[a].SkipTurns,10000)
end;



procedure chAddAmmoStore(var descr: shortstring);
begin
    descr:= ''; // avoid compiler hint
    AddAmmoStore
end;

procedure initModule;
var i: Longword;
begin
    RegisterVariable('ammloadt', @SetAmmoLoadout, false);
    RegisterVariable('ammdelay', @SetAmmoDelay, false);
    RegisterVariable('ammprob',  @SetAmmoProbability, false);
    RegisterVariable('ammreinf', @SetAmmoReinforcement, false);
    RegisterVariable('ammstore', @chAddAmmoStore , false);

    CurMinAngle:= 0;
    CurMaxAngle:= cMaxAngle;
    StoreCnt:= 0;
    ammoLoadout:= '';
    ammoProbability:= '';
    ammoDelay:= '';
    ammoReinforcement:= '';
    for i:=1 to ord(High(TAmmoType)) do
        begin
        ammoLoadout:= ammoLoadout + '0';
        ammoProbability:= ammoProbability + '0';
        ammoDelay:= ammoDelay + '0';
        ammoReinforcement:= ammoReinforcement + '0'
        end;
    FillChar(InitialCountsLocal, sizeof(InitialCountsLocal), 0)
end;

procedure freeModule;
var i: LongWord;
begin
    if StoreCnt > 0 then
        for i:= 0 to Pred(StoreCnt) do
            Dispose(StoresList[i])
end;

end.
