Map Database  •  FAQ  •  RSS  •  Login

Vatrix's Campaign Fixes

<<

Ben

User avatar

Former Site Admin

Posts: 3814

Joined: 08 Jan 2009, 23:00

Location: California - Pacific Time (UTC -8/-7 Summer Time)

Post 05 Feb 2015, 16:59

Re: Vatrix's Campaign Fixes

I was going to give my input, but I actually agree 100% with TDL...So there you go ;)
I used to spam this forum so much...
<<

cmowla

User avatar

Knight

Posts: 446

Joined: 04 Aug 2013, 19:59

KaM Skill Level: Expert

Location: United States

Post 05 Feb 2015, 18:31

Re: Vatrix's Campaign Fixes

@Everyone
Esthlos made a very good script to fulfil guideline number 5.
Look at how it works:
As Everstill said, your replay is broken.

I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible. It's very cool though.
TSK 08 test.zip
(I tested this replay, and it works.)
I don't think the towers were preloaded with stones in previous remake versions of TSK 08. Were they in the original game?

EDIT:
Oh, okay Ben. Thanks for the info.
You do not have the required permissions to view the files attached to this post.
Last edited by cmowla on 05 Feb 2015, 18:48, edited 1 time in total.
Invasion won: with 0 losses and without save reloads|TSK 20 in 4.47 minutes|Border of Life Co-op Won in 1h33m55s|The Official KaM Speedrun Page
What makes me an Expert isn't my skill in of itself but my desire to win big.
<<

Ben

User avatar

Former Site Admin

Posts: 3814

Joined: 08 Jan 2009, 23:00

Location: California - Pacific Time (UTC -8/-7 Summer Time)

Post 05 Feb 2015, 18:44

Re: Vatrix's Campaign Fixes

Towers weren't preloaded in the original game because such a thing wasn't possible (in fact, no building other than storehouse and barrack could start with wares).
I used to spam this forum so much...
<<

Esthlos

User avatar

Knight

Posts: 676

Joined: 23 Jun 2013, 16:02

KaM Skill Level: Beginner

Post 05 Feb 2015, 19:28

Re: Vatrix's Campaign Fixes

I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
What? Where? How? :(
Just when you think you know something, you have to look at it in another way, even though it may seem silly or wrong. You must try! - John Keating, "Dead Poets Society"
<<

cmowla

User avatar

Knight

Posts: 446

Joined: 04 Aug 2013, 19:59

KaM Skill Level: Expert

Location: United States

Post 05 Feb 2015, 21:59

Re: Vatrix's Campaign Fixes

I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
What? Where? How? :(
I attached a replay in the very post you quoted. You didn't see it?
Invasion won: with 0 losses and without save reloads|TSK 20 in 4.47 minutes|Border of Life Co-op Won in 1h33m55s|The Official KaM Speedrun Page
What makes me an Expert isn't my skill in of itself but my desire to win big.
<<

Esthlos

User avatar

Knight

Posts: 676

Joined: 23 Jun 2013, 16:02

KaM Skill Level: Beginner

Post 05 Feb 2015, 23:28

Re: Vatrix's Campaign Fixes

I did a test of this scripted TSK 08, and I don't think this is realistic. It just makes sucking the life out of the AI from within possible.
What? Where? How? :(
I attached a replay in the very post you quoted. You didn't see it?
How is it not realistic that you can bring an ambush party and use a single assaliant to lure the guards and kill them in a dark alley? (?)

Anyway, if you prefer it this way, this code will detect the presence of an ambush party too:
(make sure to tell the script which players to use this script for, by editing "aInvolvedAIPlayers"; the following code is already set to apply to all of TSK08's AIs)
  Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder; has to be an integer aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will try to predict if the player infiltrated an ambush party too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered part of an ambush party //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; procedure SetAIs; //Which players will use this system begin aInvolvedAIPlayers := [1, 2, 3]; //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack end; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin //Check assigned guards iCountNeeded := aGuardsRatio-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount < aGuardsTotal then Exit; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); end; end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else if States.GroupIdle(iGroup) then Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; SetAIs; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
Just when you think you know something, you have to look at it in another way, even though it may seem silly or wrong. You must try! - John Keating, "Dead Poets Society"
<<

Swiss Nisi

Laborer

Posts: 12

Joined: 12 Dec 2014, 17:26

KaM Skill Level: Beginner

Location: Switzerland

Post 06 Feb 2015, 11:57

Re: Vatrix's Campaign Fixes

1) adjusting old build order (build order like we all remember it)
Yes, it doesn't need much time anyway
2) allowing fisherman (if yes, then I'll edit the number of fish in each mission)
3) allowing market (with proper balancing and logic thinking - no knights in second mission TDL :wink: )
I think these two belong to eachother so if one is approved the other sould be as well or the other way
4) rebuilding AI cities (this is really needed, since AI cities are not working properly in Remake - iron distribution, farms, number of fields,...)
Not sure if it makes sense, when I'm going to attack an enemy i'll destroy his main buildings anyway (including builders and serfs)
5) complete reworking of AI defence positions (I've already made some changes, but it's still not perfect)
Don't see a point in that (naturally if you attack buildings or their villagers the enemy troops should respond)
6) pre-building AI cities (temporary)
As long as the AI isn't capable of building his own city, yes. Also adjust resources in storehouses (example TSK 15, black AI below right runs out of ressources pretty fast)
7) script for infinite ores for AI (better than having it in storehouse - traffic jams, script will stop working if mine is destroyed)
Yes if market is allowed and you run out of ressources like iron/gold
No if market isn't allowed, adjust resources in storehous though
8) changing colors to be close to original (I already changed colors in my 1.0 patch)
Yep
9) making impossible to sneak in and destroy AI city (with Esthlos's dynamic script)
No but same as Q5, if you attack buildings or villagers enemy troops should respond
Edit: Misunderstood it, the script is actually pretty good but like everstill said AI should respond accordingly with more troops if the first/second "wave" were wiped out.
10) making impossible to defeat any AI with starting army (normal strategy, not with cmowlas tactic, example is mission 4 where I added some troops to enemy)
If you can defeat any AI with starting army in Original it should be possible to do in the remake as well.
It's the players choice if he wants to do that or not (i don't do it, isn't as much fun as building your own city and army first and then attack)
11) changing number of starting AI troops
Depense on the Remake balance for troops, adjust if necessary
Last edited by Swiss Nisi on 06 Feb 2015, 17:15, edited 1 time in total.
<<

Everstill

Farmer

Posts: 24

Joined: 21 Jan 2015, 16:59

KaM Skill Level: Skilled

Post 06 Feb 2015, 12:20

Re: Vatrix's Campaign Fixes

What? Where? How? :(
I attached a replay in the very post you quoted. You didn't see it?
How is it not realistic that you can bring an ambush party and use a single assaliant to lure the guards and kill them in a dark alley? (?)

Anyway, if you prefer it this way, this code will detect the presence of an ambush party too:
(make sure to tell the script which players to use this script for, by editing "aInvolvedAIPlayers"; the following code is already set to apply to all of TSK08's AIs)
  Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder; has to be an integer aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will try to predict if the player infiltrated an ambush party too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered part of an ambush party //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; procedure SetAIs; //Which players will use this system begin aInvolvedAIPlayers := [1, 2, 3]; //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack end; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin //Check assigned guards iCountNeeded := aGuardsRatio-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount < aGuardsTotal then Exit; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if States.PlayerEnabled(aInvolvedAIPlayers[i]) AND States.PlayerAllianceCheck(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); end; end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else if States.GroupIdle(iGroup) then Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; SetAIs; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
The replay of cmowla worked just fine, thx!

It is indeed realistic to bring a ambush squad and kill the guards, but it not realistic for the Towns Guard Captain to send the same amount of guards over and over. The Script is working wonderfully in the Replay and the AI is reacting properly now sending guards to protect his city (instead of doing nothing or just sending all his army to the intruder). To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?

So, like in the Replay of cmowla, by killing a guard, the AI would send 2, then 3, then 4, then 5, then 6 until the intruders are killed. Of course this with your Ambush prediction (so the AI send a initial high amount of guards).

Oh, and the AI will react for killing serfs too? I will wait for another replay with your update script (I can't test myself right now).
<<

cmowla

User avatar

Knight

Posts: 446

Joined: 04 Aug 2013, 19:59

KaM Skill Level: Expert

Location: United States

Post 06 Feb 2015, 20:29

Re: Vatrix's Campaign Fixes

To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.

Excellent, excellent insight! :)
Invasion won: with 0 losses and without save reloads|TSK 20 in 4.47 minutes|Border of Life Co-op Won in 1h33m55s|The Official KaM Speedrun Page
What makes me an Expert isn't my skill in of itself but my desire to win big.
<<

Esthlos

User avatar

Knight

Posts: 676

Joined: 23 Jun 2013, 16:02

KaM Skill Level: Beginner

Post 06 Feb 2015, 21:09

Re: Vatrix's Campaign Fixes

Oh, and the AI will react for killing serfs too? I will wait for another replay with your update script (I can't test myself right now).
It should react to any attack to any building except Watch Towers, and to any attack to any unit except Warriors and Recruits.
It should also react to attacks to Serfs and Laborers, except if they are very close to a Watch Tower (this way it shouldn't trigger for Laborers repairing a Tower or Serfs resupplying it; it should also apply if the Tower is still under construction: after all, if you're attacking a Tower it is improbable you infiltrated the enemy, even if you caught it while under construction).
To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.

Excellent, excellent insight! :)
Good point indeed.

Is it better now?
(The number of sent guards should increase by 150% * every time you kill them all; the presence of an ambush party close to the first attacker is detected too)

*if you take a look at the first lines, you'll see that many of the actual values can easily be changed (text starting with // is a comment, and not part of the actually run script)
  Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army aDefendAllies = True; function SetAIs: array of Integer; //Which players will use this system begin //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack //Set it to [] to have the script work for all the AI players that are active when the mission starts result := []; end; ////////////////////////////////////Script starts here///////////////////////////////////////////// //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean; begin result := False; if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2) else result := (aPlayer1 = aPlayer2); end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin if Length(iIntruders)-1 < aIndex then Exit; //Check assigned guards iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin for i := Length(iIntruders[aIndex].aDispGuards)+iCountNeeded to Length(iIntruders[aIndex].aDispGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aDispGuards[i])); SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount <= aGuardsTotal then begin if aGuardsTotal = 0 then SetLength(iIntruders, 0); Exit; end; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; i2 := 0; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); Inc(i2); end; if i2 < iCountNeeded then aRecruitGuards(aIndex); end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if (Length(iIntruders)-1 < aIndex) OR (aIndex < 0) then Exit; if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else {if States.GroupIdle(iGroup) then} Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin iIntruders[aIndex].aKilledGuards := iIntruders[aIndex].aKilledGuards+iCount; if iIntruders[aIndex].aKilledGuards >= iIntruders[aIndex].aGuardsNeeded then begin iIntruders[aIndex].aGuardsNeeded := Round(iIntruders[aIndex].aGuardsNeeded*aReinforcementsRatio); iIntruders[aIndex].aKilledGuards := 0; end; aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; var i: Integer; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; aInvolvedAIPlayers := SetAIs; if Length(aInvolvedAIPlayers) = 0 then for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1); aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1] := i; end; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; iIntruders[Length(iIntruders)-1].aGuardsNeeded := Round(aGuardsRatio); iIntruders[Length(iIntruders)-1].aKilledGuards := 0; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; ////////////////////////////////////Script ends here///////////////////////////////////////////// procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
Just when you think you know something, you have to look at it in another way, even though it may seem silly or wrong. You must try! - John Keating, "Dead Poets Society"
<<

dicsoupcan

Moorbach's Guard

Posts: 1314

Joined: 12 Feb 2012, 21:36

KaM Skill Level: Fair

Post 06 Feb 2015, 21:16

Re: Vatrix's Campaign Fixes

oh now that i think of it, when producing the replay for tsk14 in strategy centrla i noticed one ironmine is not buldable in any way. plz fix? :D
You have enemies? Good. That means you've stood up for something, sometime in your life. ~ Winston Churchill
<<

Everstill

Farmer

Posts: 24

Joined: 21 Jan 2015, 16:59

KaM Skill Level: Skilled

Post 06 Feb 2015, 22:46

Re: Vatrix's Campaign Fixes

To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.

Excellent, excellent insight! :)
Thanks! And can you test it again with the new script? Try to sneak and destroy at all costs and let's see if the AI can handle it. If it can defeat cmowla tactics, then Esthlos script have the seal of approval!
Oh, and the AI will react for killing serfs too? I will wait for another replay with your update script (I can't test myself right now).
It should react to any attack to any building except Watch Towers, and to any attack to any unit except Warriors and Recruits.
It should also react to attacks to Serfs and Laborers, except if they are very close to a Watch Tower (this way it shouldn't trigger for Laborers repairing a Tower or Serfs resupplying it; it should also apply if the Tower is still under construction: after all, if you're attacking a Tower it is improbable you infiltrated the enemy, even if you caught it while under construction).
To make things better against exploits, can you not update the Guard value by +1 for every guard killed and after some time this number start going down?
You know, this is what was going through the back of my mind when I was producing that replay. I think this would make it much more realistic.

Excellent, excellent insight! :)
Good point indeed.

Is it better now?
(The number of sent guards should increase by 150% * every time you kill them all; the presence of an ambush party close to the first attacker is detected too)

*if you take a look at the first lines, you'll see that many of the actual values can easily be changed (text starting with // is a comment, and not part of the actually run script)
  Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable aMountedIsBetter = 1.5; //Ratio of the advantage that mounted units have when looking for a new guard aGuardsCooldown = 5; //Each how many skipped Ticks is the system managed; has to be an integer; different intruders are checked in different Ticks anyway if they are too many aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army aDefendAllies = True; function SetAIs: array of Integer; //Which players will use this system begin //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack //Set it to [] to have the script work for all the AI players that are active when the mission starts result := []; end; ////////////////////////////////////Script starts here///////////////////////////////////////////// //Global variables var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aDispGuards: array of Integer; end; aInvolvedAIPlayers: array of Integer; //UnitIDs iSkipTicks, aGuardsTotal, iCurrIntruder: Integer; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aCheckIsGuard(aWarrior: Integer): Boolean; //Check if the found soldier is already a guard var i, i2: Integer; begin result := False; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do begin if Length(iIntruders[i].aDispGuards) > 0 then for i2 := 0 to Length(iIntruders[i].aDispGuards)-1 do if aWarrior = iIntruders[i].aDispGuards[i2] then begin result := True; Exit; end; end; end; function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean; begin result := False; if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2) else result := (aPlayer1 = aPlayer2); end; end; procedure aRecruitGuards(aIndex: Integer); var i, i2, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded: Integer; iGroups: array of Integer; begin if Length(iIntruders)-1 < aIndex then Exit; //Check assigned guards iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aDispGuards); if iCountNeeded = 0 then Exit; if iCountNeeded < 0 then begin for i := Length(iIntruders[aIndex].aDispGuards)+iCountNeeded to Length(iIntruders[aIndex].aDispGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aDispGuards[i])); SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+iCountNeeded); aGuardsTotal := aGuardsTotal+iCountNeeded; Exit; end; //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iCount := iCount+States.StatUnitMultipleTypesCount(aInvolvedAIPlayers[i], [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; if iCount <= aGuardsTotal then begin if aGuardsTotal = 0 then SetLength(iIntruders, 0); Exit; end; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i], iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i]); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if States.GroupType(iGroups[i2]) <> 2 then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(States.GroupMember(iGroups[i2], 0)), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(States.GroupMember(iGroups[i2], 0))); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin if (not aCheckIsGuard(States.GroupMember(iGroups[i2], 0))) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; i2 := 0; if iChosen > 0 then for i := 0 to iCountNeeded-1 do if i < States.GroupMemberCount(iChosen) then begin SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)+1); iIntruders[aIndex].aDispGuards[Length(iIntruders[aIndex].aDispGuards)-1] := States.GroupMember(iChosen, i); Inc(aGuardsTotal); Inc(i2); end; if i2 < iCountNeeded then aRecruitGuards(aIndex); end; procedure aAttackIntruder(aIndex: Integer); var i, i2, iCount, iGroup: Integer; begin if (Length(iIntruders)-1 < aIndex) OR (aIndex < 0) then Exit; if Length(iIntruders[aIndex].aDispGuards) > 0 then begin iCount := 0; for i := 0 to Length(iIntruders[aIndex].aDispGuards)-1 do if States.UnitDead(iIntruders[aIndex].aDispGuards[i-iCount]) then begin for i2 := i-iCount to Length(iIntruders[aIndex].aDispGuards)-2 do iIntruders[aIndex].aDispGuards[i2] := iIntruders[aIndex].aDispGuards[i2+1]; Inc(iCount); end else begin iGroup := States.UnitsGroup(iIntruders[aIndex].aDispGuards[i-iCount]); if States.GroupMemberCount(iGroup) > 1 then begin iGroup := Actions.GroupOrderSplitUnit(iGroup, iIntruders[aIndex].aDispGuards[i-iCount]); Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end else {if States.GroupIdle(iGroup) then} Actions.GroupOrderAttackUnit(iGroup, iIntruders[aIndex].aID); end; if iCount > 0 then begin iIntruders[aIndex].aKilledGuards := iIntruders[aIndex].aKilledGuards+iCount; if iIntruders[aIndex].aKilledGuards >= iIntruders[aIndex].aGuardsNeeded then begin iIntruders[aIndex].aGuardsNeeded := Round(iIntruders[aIndex].aGuardsNeeded*aReinforcementsRatio); iIntruders[aIndex].aKilledGuards := 0; end; aGuardsTotal := aGuardsTotal-iCount; SetLength(iIntruders[aIndex].aDispGuards, Length(iIntruders[aIndex].aDispGuards)-iCount); end; end; end; procedure aInitAIDispatchSystem; //To be placed OnMissionStart; var i: Integer; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; aInvolvedAIPlayers := SetAIs; if Length(aInvolvedAIPlayers) = 0 then for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1); aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1] := i; end; end; procedure aManageAIDispatchSystem; //To be placed OnTick; var i: Integer; begin if Length(iIntruders) > 0 then begin if iSkipTicks < 0 then iSkipTicks := 0; if iSkipTicks > 0 then begin iSkipTicks := iSkipTicks-1; Exit; end; if (iCurrIntruder > Length(iIntruders)-1) OR (iCurrIntruder < 0) then iCurrIntruder := 0; //Check if Intruders have fled or are dead, and if there are enough guards if (States.UnitDead(iIntruders[iCurrIntruder].aID)) OR (aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange) then begin aGuardsTotal := aGuardsTotal-Length(iIntruders[iCurrIntruder].aDispGuards); for i := 0 to Length(iIntruders[iCurrIntruder].aDispGuards)-1 do if not States.UnitDead(iIntruders[iCurrIntruder].aDispGuards[i]) then Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[iCurrIntruder].aDispGuards[i])); for i := iCurrIntruder to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); iCurrIntruder := iCurrIntruder-1; end else begin aAttackIntruder(iCurrIntruder); //Also checks if the guards are dead; for this reason, it's better to do this before the recruiting aRecruitGuards(iCurrIntruder); //Recruit or dismiss guards end; Inc(iCurrIntruder); iSkipTicks := aGuardsCooldown-Length(iIntruders); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); //To be placed OnUnitAttacked and OnHouseDamaged var i: Integer; iUnits: array of Integer; aAbort: Boolean; begin if aIsMilitary then Exit; aAbort := True; if Length(aInvolvedAIPlayers) > 0 then begin for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i] = aPlayer then aAbort := False; end else Exit; if aAbort then Exit; if States.UnitDead(aIntruder) then Exit; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aIntruder then begin iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); iIntruders[Length(iIntruders)-1].aID := aIntruder; iIntruders[Length(iIntruders)-1].aOrigX := States.UnitPositionX(aIntruder); iIntruders[Length(iIntruders)-1].aOrigY := States.UnitPositionY(aIntruder); iIntruders[Length(iIntruders)-1].aAttackedPlayer := aPlayer; iIntruders[Length(iIntruders)-1].aGuardsNeeded := Round(aGuardsRatio); iIntruders[Length(iIntruders)-1].aKilledGuards := 0; SetLength(iIntruders[Length(iIntruders)-1].aDispGuards, 0); aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards aAttackIntruder(Length(iIntruders)-1); //And use them if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; ////////////////////////////////////Script ends here///////////////////////////////////////////// procedure OnMissionStart; begin aInitAIDispatchSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnTick; begin aManageAIDispatchSystem; end;
Great! Then you basically fixed many flaws that the TSK AI have. (also, love the name of your functions)

And let's wait for cmowla replay to see if the Alarm 2.0 is perfect.
<<

Vatrix

User avatar

Council Member

Posts: 410

Joined: 19 Apr 2013, 20:30

KaM Skill Level: Veteran

Location: Czech Republic

Post 06 Feb 2015, 23:16

Re: Vatrix's Campaign Fixes

If there's someone interested, here are my replays from TSK Patch 1.0:
--old--
Mission 10 is not included, cause I forgot to save it.
Last edited by Vatrix on 22 Aug 2015, 15:27, edited 1 time in total.
I fixed The Shattered Kingdom and The Peasants Rebellion here!
<<

cmowla

User avatar

Knight

Posts: 446

Joined: 04 Aug 2013, 19:59

KaM Skill Level: Expert

Location: United States

Post 06 Feb 2015, 23:36

Re: Vatrix's Campaign Fixes

And let's wait for cmowla replay to see if the Alarm 2.0 is perfect.
I have no meaningful replay to show, as the alarm seems to be very thorough. Basically it sends dozens of single leaders after all of your troops in the town, even the ones which didn't attack a civilian or a building. It appears to use the "go attack nearest troop" on a condition that the troop is in the town. This is my "worst nightmare" come true! :lol:

(Also, Everstill, no offense, but you do not have to quote everything someone posts, especially if it's an entire script.)

The game is a little "jumpy" with this script, so apart from checking for any possible bugs or code inefficiency, I think this should discourage town sneak attacks. I never liked sneaking in a village anyway.

Good work, Esthlos.
Invasion won: with 0 losses and without save reloads|TSK 20 in 4.47 minutes|Border of Life Co-op Won in 1h33m55s|The Official KaM Speedrun Page
What makes me an Expert isn't my skill in of itself but my desire to win big.
<<

Esthlos

User avatar

Knight

Posts: 676

Joined: 23 Jun 2013, 16:02

KaM Skill Level: Beginner

Post 07 Feb 2015, 13:38

Re: Vatrix's Campaign Fixes

The game is a little "jumpy" with this script, so apart from checking for any possible bugs or code inefficiency, I think this should discourage town sneak attacks. I never liked sneaking in a village anyway.
Is this one better?
I've tried to polish and streamline it as much as I could:
(P.S. Does anybody else get a weird bug where an enemy group is automatically selected?)
  Code:
//Customizable settings const aDesistRange = 625; //It is a squared value; the distance from the original attack after which the guards will give up aGuardsRatio = 2; //Number of dispatched guards per intruder aReinforcementsRatio = 1.5; //By how many times does the number of dispatched guards change every time they get all killed; set to 1 to disable aMountedIsBetter = 2.25; //It is a squared value; ratio of the advantage that mounted units have when looking for a new guard aPreventAmbush = True; //If True, the AI will predict if the player infiltrated an ambush army too aPreventAmbushRange = 625; //It is a squared value; the distance up to which a soldier will be considered an ambush army aDefendAllies = True; function SetAIs: array of Integer; //Which players will use this system begin //Format: [Player1ID, Player2ID, Player3ID] if multiple players or [Player1ID] if only one player; PlayerID is (player number)-1; allies will defend each other if closest to the attack //Set it to [] to have the script work for all the AI players that are active when the mission starts result := []; end; //Instructions: // aUpdateNumWarriors(States.UnitOwner(aUnitID)); needs to be placed OnWarriorEquipped // aUpdateAlarmSystem(aUnitID); needs to be placed OnUnitDied // aInitAlarmSystem; needs to be placed OnMissionStart // aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); needs to be placed OnUnitAttacked // aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); needs to be placed OnHouseDamaged // aRecruitGuards(-1); needs to be placed OnTick ////////////////////////////////////Script starts here///////////////////////////////////////////// //Global variables type tGuardToID = array[0..1] of Integer; var iIntruders: array of Record aID, aOrigX, aOrigY, aAttackedPlayer, aKilledGuards, aGuardsNeeded: Integer; aGuards: array of Integer; end; aInvolvedAIPlayers: array of Record aPlayer, aWarriors: Integer; end; aGuardsTotal, iCurrIntruder: Integer; function aGetDistance(aX1, aX2, aY1, aY2: Integer): Integer; //Shared function begin //No point in doing the square root too result := (aX1-aX2)*(aX1-aX2)+(aY1-aY2)*(aY1-aY2); end; function aPlayerToID(aPlayer: Integer): Integer; //Returns -1 if the player isn't involved var i: Integer; begin result := -1; if Length(aInvolvedAIPlayers) > 0 then for i := 0 to Length(aInvolvedAIPlayers)-1 do if aInvolvedAIPlayers[i].aPlayer = aPlayer then begin Result := i; Break; end; end; procedure aUpdateNumWarriors(aPlayer: Integer); var i: Integer; begin //Only melee units are considered for guard's duty i := aPlayerToID(aPlayer); if i >= 0 then aInvolvedAIPlayers[i].aWarriors := States.StatUnitMultipleTypesCount(aPlayer, [14, 15, 16, 19, 20, 21, 22, 23, 25, 26, 27]); end; procedure aUpdatePlayers; var i: Integer; iP: array of Integer; begin iP := SetAIs; if Length(iP) > 0 then begin SetLength(aInvolvedAIPlayers, Length(iP)); for i := 0 to Length(iP)-1 do aInvolvedAIPlayers[i].aPlayer := iP[i]; end else begin SetLength(aInvolvedAIPlayers, 0); for i := 0 to States.LocationCount-1 do if States.PlayerEnabled(i) AND States.PlayerIsAI(i) then begin SetLength(aInvolvedAIPlayers, Length(aInvolvedAIPlayers)+1); aInvolvedAIPlayers[Length(aInvolvedAIPlayers)-1].aPlayer := i; end; end; for i := 0 to Length(aInvolvedAIPlayers)-1 do aUpdateNumWarriors(aInvolvedAIPlayers[i].aPlayer); end; procedure aInitAlarmSystem; begin SetLength(iIntruders, 0); aGuardsTotal := 0; iCurrIntruder := 0; aUpdatePlayers; end; function aCheckUnitIsMilitary(aUnitID: Integer): Boolean; var iHouses: array of Integer; i: Integer; begin result := True; if (States.UnitDead(aUnitID)) OR (States.UnitType(aUnitID) < 0) OR (States.UnitType(aUnitID) > 12) then Exit; if (States.UnitType(aUnitID) = 0) OR (States.UnitType(aUnitID) = 9) then begin iHouses := States.PlayerGetAllHouses(States.UnitOwner(aUnitID)); for i := 0 to Length(iHouses)-1 do if States.HouseType(iHouses[i]) = 17 then if aGetDistance(States.HousePositionX(iHouses[i]), States.UnitPositionX(aUnitID),States.HousePositionY(iHouses[i]), States.UnitPositionY(aUnitID)) <= 9 then Exit; end; result := False; end; function aIntruderToID(aUnitID: Integer): Integer; //Returns -1 if not an intruder var i: Integer; begin result := -1; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if iIntruders[i].aID = aUnitID then begin result := i; Break; end; end; function aGuardToID(aUnitID: Integer): tGuardToID; //Result[0] returns -1 if not a guard var i, i2: Integer; begin result[0] := -1; if Length(iIntruders) > 0 then for i := 0 to Length(iIntruders)-1 do if Length(iIntruders[i].aGuards) >= 0 then for i2 := 0 to Length(iIntruders[i].aGuards)-1 do if iIntruders[i].aGuards[i2] = aUnitID then begin result[0] := i; result[1] := i2; Break; end; end; procedure aFreeIntruder(aIndex: Integer); //Assumes aIndex to be valid var i: Integer; begin aGuardsTotal := aGuardsTotal-Length(iIntruders[aIndex].aGuards); for i := 0 to Length(iIntruders[aIndex].aGuards)-1 do if not States.UnitDead(iIntruders[aIndex].aGuards[i]) then begin if States.GroupMemberCount(States.UnitsGroup(iIntruders[aIndex].aGuards[i])) > 1 then Actions.GroupOrderSplitUnit(States.UnitsGroup(iIntruders[aIndex].aGuards[i]), iIntruders[aIndex].aGuards[i]) else Actions.GroupOrderHalt(States.UnitsGroup(iIntruders[aIndex].aGuards[i])); end; for i := aIndex to Length(iIntruders)-2 do iIntruders[i] := iIntruders[i+1]; SetLength(iIntruders, Length(iIntruders)-1); end; function aIsPlayerAvailable(aPlayer1, aPlayer2: Integer): Boolean; begin result := False; if States.PlayerEnabled(aPlayer1) AND States.PlayerEnabled(aPlayer2) then begin if aDefendAllies then result := States.PlayerAllianceCheck(aPlayer1, aPlayer2) else result := (aPlayer1 = aPlayer2); end; end; procedure aRecruitGuards(aNum: Integer); var aIndex, i, i2, i3, iDistance, iDistanceBest, iChosen, iCount, iCountNeeded, iUnit, iUnit2: Integer; iGroups: array of Integer; iG: tGuardToID; begin if (aNum = -1) AND (Length(iIntruders) > 0) then begin Inc(iCurrIntruder); if iCurrIntruder >= Length(iIntruders) then iCurrIntruder := 0; if States.UnitDead(iIntruders[iCurrIntruder].aID) then Exit; //This isn't managed here //Check if the intruder fled if aGetDistance(iIntruders[iCurrIntruder].aOrigX, States.UnitPositionX(iIntruders[iCurrIntruder].aID), iIntruders[iCurrIntruder].aOrigY, States.UnitPositionY(iIntruders[iCurrIntruder].aID)) > aDesistRange then begin aFreeIntruder(iCurrIntruder); iCurrIntruder := iCurrIntruder-1; aRecruitGuards(-1); //Call self to repeat the distance check Exit; end; aIndex := iCurrIntruder; end else begin if (aNum >= 0) AND (aNum < Length(iIntruders)) then begin if States.UnitDead(iIntruders[aNum].aID) then Exit; //This isn't managed here aIndex := aNum end else Exit; //Also Exits if Length(iIntruders) = 0 end; iCountNeeded := iIntruders[aIndex].aGuardsNeeded-Length(iIntruders[aIndex].aGuards); if iCountNeeded > 0 then begin //Abort if the number if AI soldiers is too low; not really needed, but saves processing power iCount := 0; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i].aPlayer, iIntruders[aIndex].aAttackedPlayer) then iCount := iCount+aInvolvedAIPlayers[i].aWarriors; if iCount <= aGuardsTotal then begin if aGuardsTotal = 0 then SetLength(iIntruders, 0); //No AI soldiers left Exit; end; //Find closest group to the intruder iChosen := -1; iDistanceBest := -1; for i := 0 to Length(aInvolvedAIPlayers)-1 do if aIsPlayerAvailable(aInvolvedAIPlayers[i].aPlayer, iIntruders[aIndex].aAttackedPlayer) then begin iGroups := States.PlayerGetAllGroups(aInvolvedAIPlayers[i].aPlayer); if Length(iGroups) > 0 then for i2 := 0 to Length(iGroups)-1 do if (States.GroupType(iGroups[i2]) <> 2) AND (not States.GroupDead(iGroups[i2])) then begin //Melee guards only; if no melee is found, then the AI has more pressing problems than those intruders iUnit := States.GroupMember(iGroups[i2], 0); iG := aGuardToID(iUnit); if iG[0] < 0 then begin iDistance := aGetDistance(States.UnitPositionX(iIntruders[aIndex].aID), States.UnitPositionX(iUnit), States.UnitPositionY(iIntruders[aIndex].aID), States.UnitPositionY(iUnit)); if States.GroupType(iGroups[i2]) <> 3 then iDistance := Round(iDistance*aMountedIsBetter); if (iDistanceBest < 0) OR (iDistance < iDistanceBest) then begin iDistanceBest := iDistance; iChosen := iGroups[i2]; end; end; end; end; i2 := 0; if iChosen > 0 then for i := 0 to iCountNeeded-1 do begin SetLength(iIntruders[aIndex].aGuards, Length(iIntruders[aIndex].aGuards)+1); iIntruders[aIndex].aGuards[Length(iIntruders[aIndex].aGuards)-1] := States.GroupMember(iChosen, 0); Inc(aGuardsTotal); Inc(i2); iCount := States.GroupMemberCount(iChosen); if iCount > 1 then iUnit := Actions.GroupOrderSplitUnit(iChosen, States.GroupMember(iChosen, 0)) else iUnit := iChosen; //Link guards with the same target for i3 := 0 to Length(iIntruders[aIndex].aGuards)-1 do begin iUnit2 := States.UnitsGroup(iIntruders[aIndex].aGuards[i3]); if (States.GroupType(iUnit2) = States.GroupType(iUnit)) AND (iUnit <> iUnit2) then begin Actions.GroupOrderLink(iUnit, iUnit2); iUnit := iUnit2; Break; end; end; Actions.GroupOrderAttackUnit(iUnit, iIntruders[aIndex].aID); if iCount <= 1 then Break; end; if i2 < iCountNeeded then aRecruitGuards(aIndex); end; end; procedure aAlarm(aIntruder, aPlayer: Integer; aIsMilitary, aCheckForAmbush: Boolean); var i: Integer; iUnits: array of Integer; begin if aIsMilitary then Exit; if aPlayerToID(aPlayer) < 0 then Exit; //Player not involved if States.UnitDead(aIntruder) then Exit; i := aIntruderToID(aIntruder); if i >= 0 then begin //Already detected iIntruders[i].aOrigX := States.UnitPositionX(aIntruder); iIntruders[i].aOrigY := States.UnitPositionY(aIntruder); Exit; end; SetLength(iIntruders, Length(iIntruders)+1); with iIntruders[Length(iIntruders)-1] do begin aID := aIntruder; aOrigX := States.UnitPositionX(aIntruder); aOrigY := States.UnitPositionY(aIntruder); aAttackedPlayer := aPlayer; aGuardsNeeded := Round(aGuardsRatio); aKilledGuards := 0; SetLength(aGuards, 0); end; aRecruitGuards(Length(iIntruders)-1); //Recruit the first guards if aCheckForAmbush then begin iUnits := States.PlayerGetAllUnits(States.UnitOwner(aIntruder)); for i := 0 to Length(iUnits)-1 do if States.UnitType(iUnits[i]) >= 14 then if aGetDistance(States.UnitPositionX(aIntruder), States.UnitPositionX(iUnits[i]), States.UnitPositionY(aIntruder), States.UnitPositionY(iUnits[i])) <= aPreventAmbushRange then aAlarm(iUnits[i], aPlayer, False, False); end; end; procedure aUpdateAlarmSystem(aUnitID: Integer); //I'm assuming that someone died var i: Integer; iG: tGuardToID; begin if (States.UnitType(aUnitID) >= 14) AND (States.UnitType(aUnitID) <= 27) then begin //Warriors only aUpdateNumWarriors(States.UnitOwner(aUnitID)); i := aIntruderToID(aUnitID); if i >= 0 then begin aFreeIntruder(i); end; iG := aGuardToID(aUnitID); if iG[0] >= 0 then begin aGuardsTotal := aGuardsTotal-1; Inc(iIntruders[iG[0]].aKilledGuards); if iIntruders[iG[0]].aKilledGuards >= iIntruders[iG[0]].aGuardsNeeded then begin iIntruders[iG[0]].aGuardsNeeded := Round(iIntruders[iG[0]].aGuardsNeeded*aReinforcementsRatio); iIntruders[iG[0]].aKilledGuards := 0; end; for i := iG[1] to Length(iIntruders[iG[0]].aGuards)-2 do iIntruders[iG[0]].aGuards[i] := iIntruders[iG[0]].aGuards[i+1]; SetLength(iIntruders[iG[0]].aGuards, Length(iIntruders[iG[0]].aGuards)-1); end; end; end; ////////////////////////////////////Script ends here///////////////////////////////////////////// procedure OnMissionStart; begin aInitAlarmSystem; end; procedure OnUnitAttacked(aUnitID, AttackerID: Integer); begin aAlarm(AttackerID, States.UnitOwner(aUnitID), aCheckUnitIsMilitary(aUnitID), aPreventAmbush); end; procedure OnHouseDamaged(aHouseID, AttackerID: Integer); begin aAlarm(AttackerID, States.HouseOwner(aHouseID), (States.HouseType(aHouseID) = 17), aPreventAmbush); end; procedure OnUnitDied(aUnitID, aKillerID: Integer); begin aUpdateAlarmSystem(aUnitID); end; procedure OnWarriorEquipped(aUnitID, aGroupID: Integer); begin aUpdateNumWarriors(States.UnitOwner(aUnitID)); end; procedure OnTick; begin aRecruitGuards(-1); end;
Good work, Esthlos.
:mrgreen:
Just when you think you know something, you have to look at it in another way, even though it may seem silly or wrong. You must try! - John Keating, "Dead Poets Society"

Return to “Map Design”

Who is online

Users browsing this forum: No registered users and 14 guests