/*
    BattleRPG Copyright (C) 2007-2008 Nico de Vries.

    This file is part of BattleRPG.

    BattleRPG 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, either version 3 of the License, or
    (at your option) any later version.

    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, see http://www.gnu.org/licenses/.
*/

`include (BattleRPG/Classes/BattleRPG.uci) 

class BattleRPG extends UTMutator config(BattleRPG) dependson(BattleStaticFunctions, BattlePersistentPlayerDatabase);

// Connection to the database
var BattlePersistentPlayerDatabase BPD;
var BattlePersistentPlayerBuildDatabase BPBD;
var BattlePersistentPlayerBackupDatabase BAK;
var FileWriter FW;

// Debug settings 
var config bool bDebugmode;

// Leveling settings
var config int RPGDollarsPerLevel;
var config int NeededXPFactor;
var config int MinimumLevel;

// Ability management
struct AbilityAndLevel {
  var name ID;
  var int Level;
};

struct DynamicAbilityRequirementsWrapper { 
  var AbilityAndLevel This; //  E.g. "before you can buy XYZ level 1+"
  var array<AbilityAndLevel> Required; // E.g. "you need ABC level 2+ AND DEF level 1+"
  var array<AbilityAndLevel> NotAllowedWith; // E.g. "you can't buy XYZ level 1 if you have GHI level 1+ AND JKL level 4+"  
}; // OR is achieved by defining multiple DynamicAbilityRequirements for the same ability

struct DynamicAbilityWrapper {
  var name ID;
  var class<BattleAbility> AbilityClass;
  var string AbilityName;
  var string AbilityDescription;
  var int AbilityMaxLevel; // An ability has level 1 .. AbilityMaxLevel.
  var int AbilityLevel1Cost; // Cost of the first level.
  var int AbilityCostIncreasePerLevel; // Each level can be more expensive than the previous one, or not.
};

var config array<DynamicAbilityWrapper> DynamicAbilities; // Definition (config) of dynamic abilities

var array<BattleAbility> AvailableAbilities; // Actual instances of (dynamic) abilities

var config array<DynamicAbilityRequirementsWrapper> DynamicAbilityRequirements;
// DynamicAbilityRequirements=(This=(Id="QuickFoot",Level=2),Required=((Id="SuperJump",Level=2),(Id="WeaponSpeed",Level=2)),NotAllowedWith=)
// DynamicAbilityRequirements=(This=(Id="QuickFoot",Level=4),Required=,NotAllowedWith=((Id="HealthBonus",Level=2)))
// DynamicAbilityRequirements=(This=(Id="HealthBonus",Level=2),Required=,NotAllowedWith=((Id="QuickFoot",Level=4)))


// Magic weapon management
var config int PercentageChanceOfWeaponMagic;
struct WeaponMagicWrapper {
  var int Chance;
  var class<BattleWeaponMagic> WeaponMagicClass;
};
var config array<WeaponMagicWrapper> MagicWeapons;
var array<BattleWeaponMagic> AvailableWeaponMagic;

struct ReplacementInfo {
  var name OldClassName;
  var string NewClassPath;
};
var array<ReplacementInfo> WeaponsToReplace;

`if(`isdefined(BRPG_Build4UT3Invasion))
  var bool bHasUT3InvasionScoreboardExtension;
`endif

// Abuse GetSeamlessTravelActorList as some sort of ExitMutator, if someone has a better solution please let me know
function GetSeamlessTravelActorList(bool bToEntry, out array<Actor> ActorList)
{
  local Controller C;

  if (FW != None) FW.CloseFile ();
  
  foreach WorldInfo.AllControllers(Class'Controller', C) {
    if (BattlePlayerController(C) != None) {
      BattlePlayerController(C).LastActiveWeapon = None; // No resurection between games
    }
  }

  Super.GetSeamlessTravelActorList(bToEntry, ActorList);
}

// Essentially this function converts the old "static" abilities into dynamic abilities
function AddDynamicAbility (name ID, class<BattleAbility> TheClass)
{
  local DynamicAbilityWrapper DAW;
  local BattleAbility BA;

  DAW.ID = ID; 
  DAW.AbilityClass = TheClass; 
  BA = New DAW.AbilityClass;
  DAW.AbilityName = BA.AbilityName;
  DAW.AbilityDescription = BA.AbilityDescription;
  DAW.AbilityMaxLevel = BA.AbilityMaxLevel;
  DAW.AbilityLevel1Cost = BA.AbilityLevel1Cost;
  DAW.AbilityCostIncreasePerLevel = BA.AbilityCostIncreasePerLevel;
  DynamicAbilities.AddItem (DAW);
}

function InitMutator(string Options, out string ErrorMessage)
{
  local UTGame Game;
  local WeaponMagicWrapper MWW;
  local int i;
  local BattleAbility BA;
  local BattleWeaponMagic BWM;

  WorldInfo.Game.AddGameRules(class'BattleRPG.BattleGameRules');

  if (MagicWeapons.length==0) {
    MWW.chance = 5;
    MWW.WeaponMagicClass = class'BattleWeaponMagic_IncreasedDamage';
    MagicWeapons.AddItem (MWW);
    MWW.chance = 1;
    MWW.WeaponMagicClass = class'BattleWeaponMagic_ReducedDamage';
    MagicWeapons.AddItem (MWW);
    SaveConfig();
  }
  if (DynamicAbilities.length==0) { 
    AddDynamicAbility ('HealthBonus', Class'BattleRPG.BattleAbility_HealthBonus');
    AddDynamicAbility ('HealthRegen', Class'BattleRPG.BattleAbility_HealthRegen');
    AddDynamicAbility ('DamageBonus', Class'BattleRPG.BattleAbility_DamageBonus');
    AddDynamicAbility ('DamageReduction', Class'BattleRPG.BattleAbility_DamageReduction');
    AddDynamicAbility ('Vampirism', Class'BattleRPG.BattleAbility_Vampirism');
    AddDynamicAbility ('WeaponSpeed', Class'BattleRPG.BattleAbility_WeaponSpeed');
    AddDynamicAbility ('AmmoRegen', Class'BattleRPG.BattleAbility_AmmoRegen');
    AddDynamicAbility ('QuickFoot', Class'BattleRPG.BattleAbility_QuickFoot');
    AddDynamicAbility ('SuperJump', Class'BattleRPG.BattleAbility_SuperJump');
    AddDynamicAbility ('DodgeJumping', Class'BattleRPG.BattleAbility_DodgeJumping');
    AddDynamicAbility ('LoadedWeapons', Class'BattleRPG.BattleAbility_LoadedWeapons');
    AddDynamicAbility ('LoadedArtifacts', Class'BattleRPG.BattleAbility_LoadedArtifacts');
    AddDynamicAbility ('LoadedMagic', Class'BattleRPG.BattleAbility_LoadedMagic');
    AddDynamicAbility ('WeaponResurrection', Class'BattleRPG.BattleAbility_WeaponResurrection');
    AddDynamicAbility ('ManaRegen', Class'BattleRPG.BattleAbility_ManaRegen');
    AddDynamicAbility ('ManaBonus', Class'BattleRPG.BattleAbility_ManaBonus');
    AddDynamicAbility ('AmmoBonus', Class'BattleRPG.BattleAbility_AmmoBonus');
    SaveConfig();
  }
  if (RPGDollarsPerLevel < 1) {
    RPGDollarsPerLevel = 5;
    SaveConfig();
  }
  if (NeededXPFactor < 1) {
    NeededXPFactor = 100;
    SaveConfig();
  }
  if (MinimumLevel < 1) {
    MinimumLevel = 1;
    SaveConfig();
  }
  if (PercentageChanceOfWeaponMagic < 1) {
    PercentageChanceOfWeaponMagic = 50;
    SaveConfig();
  }

  for (i=0; i<DynamicAbilities.Length; i++) {
    BA = New DynamicAbilities[i].AbilityClass;
    BA.AbilityName = DynamicAbilities[i].AbilityName;
    BA.AbilityDescription = DynamicAbilities[i].AbilityDescription;
    BA.AbilityMaxLevel = DynamicAbilities[i].AbilityMaxLevel;
    BA.AbilityLevel1Cost = DynamicAbilities[i].AbilityLevel1Cost;
    BA.AbilityCostIncreasePerLevel = DynamicAbilities[i].AbilityCostIncreasePerLevel;
    AvailableAbilities.AddItem (BA);
  } 

  for (i=0; i<MagicWeapons.Length; i++) {
    BWM = New MagicWeapons[i].WeaponMagicClass;
    AvailableWeaponMagic.AddItem (BWM);
    BWM.SaveConfig();
  }

  Game = UTGame(WorldInfo.Game);
  Game.PlayerControllerClass = class'BattlePlayerController';

  BPD = New (self) class'BattleRPG.BattlePersistentPlayerDatabase';
  if (bDebugMode) {
    FW = Spawn (class'FileWriter', self);
    FW.OpenFile ("BattlePersistentPlayerDatabase", FWFT_Log, ".log", true, true);
    BPD.FW = FW;
  }
  BPD.BRPG = self;
  BPBD = New (BPD) class'BattleRPG.BattlePersistentPlayerBuildDatabase';
  BPBD.BRPG = self;
  BPD.BPBD = BPBD;
  BAK = New (BPD) class'BattleRPG.BattlePersistentPlayerBackupDatabase'; 
  BPD.BAK = BAK;  
  BPD.Commit();

  SetTimer (60.0, True, 'CommitDatabase'); // Save the database to disk once every 60 seconds (useful if the server decides to crash)

  if (InStr(WorldInfo.GetGameClass().name, "Ons")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = Class'BattlePRIONS';
  } else if (InStr(WorldInfo.GetGameClass().name, "CTF")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = Class'BattlePRI';
  } else if (InStr(WorldInfo.GetGameClass().name, "Duel")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = Class'BattlePRIDuel';
  } else if (InStr(WorldInfo.GetGameClass().name, "Team")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = Class'BattlePRI';
  } else if (InStr(WorldInfo.GetGameClass().name, "Death")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = Class'BattlePRI';
`if(`isdefined(BRPG_Build4GaltanorsInvasion))
  } else if (InStr(WorldInfo.GetGameClass().name, "Galtanor")!=-1) {
    WorldInfo.Game.PlayerReplicationInfoClass = class'BattlePRIInvasion';
`endif
`if(`isdefined(BRPG_Build4UT3Invasion))
  } else if (InStr(WorldInfo.GetGameClass().name, "Invasion")!=-1) {
`endif
    WorldInfo.Game.PlayerReplicationInfoClass = class'BattlePRIInvasion';
  }

  Super.InitMutator(Options, ErrorMessage);
}

`if(`isdefined(BRPG_Build4GaltanorsInvasion))
function Tick(float DeltaTime)
{
  local InvGameReplicationInfo InvGRI;

  InvGRI = InvGameReplicationInfo(WorldInfo.GRI);
  if (InvGRI != None) {
    InvGRI.bIsRunningBattleMod = true;
  }
}
`endif

function PostBeginPlay()
{
  local UTGame Game;
  local int i, Index;

  Super.PostBeginPlay();

  Game = UTGame(WorldInfo.Game);
  if (Game != None) {
    for (i = 0; i < Game.DefaultInventory.length; i++) {
      if (Game.DefaultInventory[i] != None) {
        Index = WeaponsToReplace.Find('OldClassName', Game.DefaultInventory[i].Name);
        if (Index != INDEX_NONE) {
          Game.DefaultInventory[i] = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
		}
      }
    }
  }
}

function CommitDatabase ()
{
  if (BPD != None) BPD.Commit();
}

simulated function ModifyPlayer(Pawn Other)
{
  local UTPawn P;
  local PlayerController PC;
  local BattlePlayerController BPC;
  local int i;
  local int Level;
  local BattleAbility BA;
  local BattleArtifact BAF;
  local BattleGenInvItem4Pawns BGI; 
  local BPRIDATA BD;

`if(`isdefined(BRPG_Build4UT3Invasion))
  if (!bHasUT3InvasionScoreboardExtension && UTPGameReplicationInfo(WorldInfo.GRI) != None) {
    UTPGameReplicationInfo(WorldInfo.GRI).AddScoreboardExtension(class'BattleRPG.BRPGExt_UT3InvasionRpgScoreboardExtension');
    bHasUT3InvasionScoreboardExtension = true;
  }
`endif

  if (UTPawn (Other)!=None) {
    PC = PlayerController (Other.Owner);
    if (PC != None) {
      // Move data for this player from persistant database to memory (function autodetects if this is needed)
      BPD.LoadPlayerData (PC.PlayerReplicationInfo);

      // Create a BattleGenInvItem4Pawns for this pawn
      P = UTPawn(Other);
      BGI = BattleGenInvItem4Pawns(Other.CreateInventory(class'BattleRPG.BattleGenInvItem4Pawns'));
      if (P != None) {
        // Prepare proper handling of the dodgejump ability
        BD = class'BattleStaticFunctions'.static.ReadBPRI (PC.PlayerReplicationInfo);
        BD.bAllowDodgeJumping = false; // Changed to true by the DodgeJump ability if the player has that.
        class'BattleStaticFunctions'.static.WriteBPRI (PC.PlayerReplicationInfo, BD);

        // Give the players the default artifacts
        BAF = New (P) class'BattleRPG.BattleArtifactInstant_Bonus';
        BAF.WorldInfo = WorldInfo;
        BGI.AddActiveArtifact (BAF);
        BAF = New (P) class'BattleRPG.BattleArtifactDraining_SpiderPig';
        BAF.WorldInfo = WorldInfo;
        BGI.AddActiveArtifact (BAF);
        BAF = New (P) class'BattleRPG.BattleArtifactInstant_ManaGambling';
        BAF.WorldInfo = WorldInfo;
        BGI.AddActiveArtifact (BAF);

        // Execute ServerModifyPlayer for all abilities the player has
        for (i=0; i<DynamicAbilities.Length; i++) {
          Level = BPD.BPBD.GetPlayerLevel (name(Other.PlayerReplicationInfo.PlayerName), i);
          if (Level>0) { // Does the player have this ability?
            // Create a new instance to allow the ability class to store data etc.
            BA = New (P) DynamicAbilities[i].AbilityClass; // Do not copy settings since these are not needed here
            BGI.AddActiveAbility (BA, Level);
            if (WorldInfo.NetMode != NM_Client) {
              BA.ServerModifyPlayer (P, Level); // Execute ability specific logic.
            }
          }
        }
        
        // Check if SelectedArtifact is valid
        BPC = BattlePlayerController (PC);
        if (BPC != None) {
          if (BPC.SelectedArtifact >= BGI.ActiveArtifactsLength) BPC.SelectedArtifact = BGI.ActiveArtifactsLength - 1; // So -1 means player has no artifacts
          if (BPC.SelectedArtifact >= 0) {
            BPC.SelectedArtifactName = BGI.GetArtifact(BPC.SelectedArtifact).ArtifactDescription();
            BPC.SelectedArtifactCost = BGI.GetArtifact(BPC.SelectedArtifact).ArtifactCostDescription();
            If (BPC.bDrainingArtifactActive) {
              BattleArtifactDraining(BGI.GetArtifact(BPC.SelectedArtifact)).StopDrainingArtifact (P, true);
              BPC.bDrainingArtifactActive = false;
            }
          }
        }
      }     
    }
  }

  Super.ModifyPlayer(Other);
}

function bool CheckReplacement(Actor Other)
{
  local UTWeaponPickupFactory WeaponPickup;
  local UTWeaponLocker Locker;
  local int i, Index;
`if(`isdefined(BRPG_Build4GaltanorsInvasion))
  local InvMonsterPawn Monster;
`endif

  WeaponPickup = UTWeaponPickupFactory(Other);
  if (WeaponPickup != None) {
    if (WeaponPickup.WeaponPickupClass != None) {
      Index = WeaponsToReplace.Find('OldClassName', WeaponPickup.WeaponPickupClass.Name);
      if (Index != INDEX_NONE) {
        WeaponPickup.WeaponPickupClass = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
        WeaponPickup.InitializePickup();
      }
    }
  }	else {
    Locker = UTWeaponLocker(Other);
    if (Locker != None) {
      for (i = 0; i < Locker.Weapons.length; i++) {
        if (Locker.Weapons[i].WeaponClass != None) {
          Index = WeaponsToReplace.Find('OldClassName', Locker.Weapons[i].WeaponClass.Name);
          if (Index != INDEX_NONE) {
            Locker.ReplaceWeapon(i, class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class')));
          }
        }
      }
	}
  }
`if(`isdefined(BRPG_Build4GaltanorsInvasion))
  Monster = InvMonsterPawn(Other);
  if (Monster != None) {
    for (i = 0; i < Monster.MonsterItems.length; i++) {
      if (Monster.MonsterItems[i] != None) {
        Index = WeaponsToReplace.Find('OldClassName', Monster.MonsterItems[i].Name);
        if (Index != INDEX_NONE) {
          Monster.MonsterItems[i] = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
        }
      }
    }
    return true;
  }
`endif
  return true;
}



function DriverEnteredVehicle(Vehicle V, Pawn P)
{
  local BattlePlayerController BPC;
  local BattleArtifact BAF;  
  local BattleGenInvItem4Pawns BGI;
  local int i;

  if (P == None || UTPawn (P) == None) return;
  BPC = BattlePlayerController (P.Owner);
  if (BPC==None) {
    BPC = BattlePlayerController (V.Controller);
  }
  BGI = BattleGenInvItem4Pawns (P.FindInventoryType(class'BattleRPG.BattleGenInvItem4Pawns'));
  if (BGI != None) {
    for (i=0; i<BGI.ActiveAbilitiesLength; i++) {    
      BGI.GetAbility(i).LLDriverEnteredVehicle (V, P, BGI.GetAbilityLevel(i));
    }  
  }
  if (BPC != None && BGI != None && BPC.bDrainingArtifactActive && BPC.PlayerReplicationInfo != None) {
    BAF = BGI.GetArtifact (BPC.SelectedArtifact);
    if (BattleArtifactDraining(BAF) != None) {
      BattleArtifactDraining(BAF).StopDrainingArtifact (UTPawn (P), false);
      BPC.bDrainingArtifactActive = false;
    }
  } 
}

function CreateWeaponMagic (UTWeapon UTW, out BattleWeaponMagic ActiveBWM, out int ActiveBWMLevel, optional string Special="")
{ 
  local array<BattleWeaponMagic> AvailableWeaponMagicForThisWeapon;
  local array<int> AvailableWeaponMagicForThisWeapon_Chance;
//  local BWEAPDATA BWEAP;
  local int RandomSlots;
  local int r;
  local int i;

  if (!class'BattleStaticFunctions'.static.HasWEAP (UTW)) return;

  // Decide if the weapon should become magic
  if (Special != "alwaysmagic" && Special != "alwayspositivemagic") {
    if (Rand(100)+1 > PercentageChanceOfWeaponMagic) return; 
  }
 
  // Determine which types of magic are available for this weapon
  for (i=0; i<AvailableWeaponMagic.Length; i++) {
    if (AvailableWeaponMagic[i].AllowedFor (UTW)) {
      if (Special != "alwayspositivemagic" || AvailableWeaponMagic[i].bPositiveMagic) {
        AvailableWeaponMagicForThisWeapon.AddItem (AvailableWeaponMagic[i]);
        AvailableWeaponMagicForThisWeapon_Chance.AddItem (MagicWeapons[i].Chance);
        RandomSlots += MagicWeapons[i].Chance; // The higher the chance for this magic type, the more slots it gets
      }
    }
  }

  // Based on the configured chances pick a random (and available) magic type
  r = Rand (RandomSlots)+1;
  for (i=0; i<AvailableWeaponMagicForThisWeapon.Length; i++) {
    if (r <= AvailableWeaponMagicForThisWeapon_Chance[i]) { // Jackpot
      ActiveBWM = New (UTW) AvailableWeaponMagicForThisWeapon[i].Class;
      ActiveBWMLevel = Rand(AvailableWeaponMagicForThisWeapon[i].MagicMaxLevel)+1;
//      BWEAP = class'BattleStaticFunctions'.static.ReadWEAP (UTW);
//      BWEAP.MagicMaterial = ActiveBWM.MagicMaterial;
//      class'BattleStaticFunctions'.static.WriteWEAP (UTW, BWEAP);
      return;
    } else {
      r -= AvailableWeaponMagicForThisWeapon_Chance[i];
    }
  }

  // Usually we do not get here, but perhaps the server admin excluded all magic types for this weapon
}

defaultproperties
{
  bDebugmode = false;
  WeaponsToReplace(0)=(OldClassName="UTWeap_Avril_Content",NewClassPath="BattleRPG.BattleWeapon_Avril")
  WeaponsToReplace(1)=(OldClassName="UTWeap_BioRifle_Content",NewClassPath="BattleRPG.BattleWeapon_BioRifle")
  WeaponsToReplace(2)=(OldClassName="UTWeap_Enforcer",NewClassPath="BattleRPG.BattleWeapon_Enforcer")
  WeaponsToReplace(3)=(OldClassName="UTWeap_FlakCannon",NewClassPath="BattleRPG.BattleWeapon_FlakCannon")
  WeaponsToReplace(4)=(OldClassName="UTWeap_ImpactHammer",NewClassPath="BattleRPG.BattleWeapon_ImpactHammer")
  WeaponsToReplace(5)=(OldClassName="UTWeap_LinkGun",NewClassPath="BattleRPG.BattleWeapon_LinkGun")
  WeaponsToReplace(6)=(OldClassName="UTWeap_RocketLauncher",NewClassPath="BattleRPG.BattleWeapon_RocketLauncher")
  WeaponsToReplace(7)=(OldClassName="UTWeap_ShockRifle",NewClassPath="BattleRPG.BattleWeapon_ShockRifle")
  WeaponsToReplace(8)=(OldClassName="UTWeap_SniperRifle",NewClassPath="BattleRPG.BattleWeapon_SniperRifle")
  WeaponsToReplace(9)=(OldClassName="UTWeap_Stinger",NewClassPath="BattleRPG.BattleWeapon_Stinger")
  Name="BattleRPG"
}