Claude responded: That's a useful clue. Let me look closely at how multiplayer turns work in the script. Re-reading the ResetViewDistance function and surrounding context in the patched V2 fileRe-reading the ResetViewDistance function and surrounding context in the patched V2 fileThe patch works in single player mode, but doesn't consistently work in multiplayer mode.
There's the problem — and it's actually a logic flaw that was in the original mod design, now exposed more clearly by our fix. Look at line 53:
if (IsMultiplayer() == 1 || IsAITurn() == 0)
In single player, this works fine:
Human turn → IsAITurn() == 0 is true → restricted LOS ✓
AI turn → falls to else → full LOS ✓
In multiplayer, IsMultiplayer() == 1 is always true, so the condition is always true regardless of whose turn it is. That means every unit — both players' — always gets MODDED_LOS. That's probably the inconsistency players are seeing: in multiplayer it may restrict LOS when it shouldn't, or apply it at the wrong moment depending on when ResetViewDistance is called per side.
The original intent seems to have been "in multiplayer, always restrict because there's no AI" — but in a hotseat or network multiplayer game there are still two human sides taking turns, and the function may be called during either side's turn.
The fix depends on one question: in multiplayer, should both sides always have restricted LOS (9 tiles), or should only the currently active side?
If both sides always restricted (simplest, probably the intent): the current logic is actually correct for multiplayer — the || short-circuits and always applies MODDED_LOS. The inconsistency may be coming from something else, like ResetViewDistance being called at a different point in the turn cycle in multiplayer.
If only the active side restricted: the condition needs to change to just IsAITurn() == 0, dropping the IsMultiplayer() check entirely, since IsAITurn() should correctly reflect whose turn it is in multiplayer too.
Could you clarify — in multiplayer, is the intention that both human players always have the 9-tile LOS limit? Or is the report more that it sometimes works and sometimes doesn't (i.e. it's unreliable rather than wrong)?
That simplifies things nicely.That simplifies things nicely. The logic should be:Both sides should always be restricted to 9 tlles, starting from deployment.
Always apply MODDED_LOS — no need to check for AI or multiplayer at all, since all human players should always be restricted
Including deployment — so the IsDeploying() guard we added actually needs to come out too, since you want the restriction active from the very start
The entire condition block can be replaced with a single SetAttrib call. Here's what the function becomes:
bsf// MODDED: SIMPLIFIED FUNCTION SPECIFYING THE LINE OF SIGHT RANGE IN 10'S (I.E., 100 -> 10 TILES in LOS, 50 -> 5 TILES, ETC.)
// MODDED: All human players always restricted to MODDED_LOS from deployment onwards. AI retains full LOS in single player.
FUNCTION ResetViewDistance(me)
{
if (IsAITurn() == 0) // MODDED: HUMAN PLAYERS (includes multiplayer, always restricted)
{
SetAttrib(me, "LOS", MODDED_LOS); // MODDED: SET TO MODDED_LOS AS DEFINED ABOVE
}
else // MODDED: AI (single player only)
{
SetAttrib(me, "LOS", MODDED_LOS_AI); // MODDED: SET TO MODDED_LOS_AI AS DEFINED ABOVE
}
SetAttrib (me, "CoverLOS", GetBaseAttrib(me, "CoverLOS")) ;
}
Wait — but this brings back the original Turn 1 bug in single player, since IsAITurn() is unreliable during deployment. However, you've now said restriction should apply from deployment, so on Turn 1 if IsAITurn() misfires and gives full LOS briefly, that's still a problem.
Before I produce the files, let me ask: in single player, should the AI also be restricted to 9 tiles, or should AI keep full LOS? That's the key remaining question — if AI should also always be restricted, we can drop all the conditions entirely and just always set MODDED_LOS, which also eliminates the Turn 1 bug completely.
Got it.Got it. So the rules are:The Ai should have full LOS as it's not smart enough to plan for limited LOS.
Human players (single or multiplayer) → always 9 tiles, from deployment onwards
AI → always full LOS
The tricky part remains: in single player, IsAITurn() is unreliable during deployment, but you want humans restricted from deployment. Since in multiplayer IsAITurn() should always return 0 (there is no AI), and in single player during deployment the function misfires and returns 1 (AI) when it should return 0 (human) — we need a way to catch that edge case.
The safest approach is to combine both checks: apply MODDED_LOS if it's either a multiplayer game or not an AI turn. This is actually what the original mod had — the problem wasn't that logic, it was the missing deployment coverage. Since you now want restriction during deployment too, we simply remove the IsDeploying() guard and restore the IsMultiplayer() check:
bsfif (IsMultiplayer() == 1 || IsAITurn() == 0) // MODDED: HUMAN PLAYERS
In multiplayer: IsMultiplayer() == 1 is always true → always restricted ✓
In single player, human turn: IsAITurn() == 0 is true → restricted ✓
In single player, AI turn: both false → full LOS ✓
In single player, deployment: IsMultiplayer() is 0 and IsAITurn() misfires to 1 → falls to else, gives AI full LOS briefly... still the same Turn 1 bug
So we're back to the same problem. The only clean solution is: can you confirm whether deployment in single player is always the human player's phase, never the AI's? If so, we could add a separate IsDeploying() branch that always applies MODDED_LOS, covering that window explicitly:
bsfFUNCTION ResetViewDistance(me)
{
if (IsDeploying() == 1 || IsMultiplayer() == 1 || IsAITurn() == 0) // MODDED: HUMAN PLAYERS + DEPLOYMENT
{
SetAttrib(me, "LOS", MODDED_LOS);
}
else // MODDED: AI
{
SetAttrib(me, "LOS", MODDED_LOS_AI);
}
SetAttrib (me, "CoverLOS", GetBaseAttrib(me, "CoverLOS")) ;
}
This would mean: restrict LOS during deployment always, restrict during any human turn, give AI full LOS otherwise. Shall I go ahead with this approach for both files?
That's a fair point — if the game has autodeploy, then the AI does participate in deployment in some form, so we can't safely assume deployment is always a hum…That's a fair point — if the game has autodeploy, then the AI does participate in deployment in some form, so we can't safely assume deployment is always a human-only phase. Applying MODDED_LOS unconditionally during deployment could interfere with that.I have no idea about how AI deployment is handled by the game. There clearly are some routines used, as players can opt for autodeploy and the computer will deploy your forces for you.
The honest answer is we're hitting the limits of what can be determined just from this script file — the deployment AI logic lives in the other included files (StartTurnPreTools.bsf, AITools.bsf etc.) which we don't have access to.
Given that uncertainty, I'd suggest the most conservative safe fix is to go back to the version we already produced — V2 for Medieval, and the Ancients patch — which:
Restricts LOS for human players during normal play ✓
Gives AI full LOS ✓
Fixes the Turn 1 single player bug via the IsDeploying() guard ✓
Leaves deployment phase untouched to avoid breaking autodeploy ✓
The only remaining known limitation is that LOS restriction doesn't kick in during the deployment phase for human players. Given that deployment is a pre-game setup screen rather than active battlefield play, that's probably an acceptable trade-off compared to the risk of breaking autodeploy.
Would you like to go with the existing V2 files as-is, or would you prefer to test the IsDeploying() == 1 || approach in-game knowing it carries some risk around autodeploy?
Yes please — that would really help. The most useful ones would be:I can share the other script files if you need them.
StartTurnPreTools.bsf — this is almost certainly where ResetViewDistance gets called at the start of each turn, so it would show us exactly when and how it's triggered
AITools.bsf — this would tell us how the AI handles deployment, including autodeploy
If you can share those two to start with, that should give us enough to answer the deployment question confidently and work out the correct final fix.
Interesting — ResetViewDistance is not called anywhere in StartTurnPreTools.bsf. Let me search for it in AITools.bsf and check where it actually gets called from. Now I have a very clear picture. Let me check what function ApplyLOSAllUnits is called from (line 3023): This is very revealing. Now I have a complete picture. Here's what I found:
ResetViewDistance is only called via ApplyLOSAllUnits(), which is called at the end of RestoreUnitData(). That's the function used when a unit swaps type — e.g. cavalry dismounting, war wagons deploying/undeploying. It is not called at the start of every turn.
This means the previous AI's patch introduced ApplyLOSAllUnits() as what it thought was a turn-start hook, but it's actually only triggered by unit type-swap events. The LOS restriction is therefore not being systematically applied at turn start at all — it only fires incidentally when a unit changes type.
This explains the inconsistency perfectly:
Some turns it appears to work → units happened to swap type that turn, triggering ApplyLOSAllUnits()
First turn it doesn't work → no unit swaps have happened yet
Multiplayer inconsistency → same reason, it's event-driven rather than turn-driven
The real fix needs ResetViewDistance called properly on every unit at the start of every turn. Looking at StartTurnPreTools.bsf, the right place would be inside StartTurnPreHousekeeping() — specifically in the loop that already iterates over all units on both sides (lines 19–34). That's where LOS should be reset each turn.
To do this properly I'd need to see the file where StartTurnPreHousekeeping is called from, and ideally Core.bsf or whatever the main turn engine file is — as line 1 of StartTurnPreTools.bsf says it "needs to be included in the Core.BSF StartTurn(side) function." Do you have Core.bsf available?
Now I have the complete picture. This is very clear. Here's what I found:Yes
The Real Problem
ResetViewDistance should be called once per turn for every unit. The right place to do this is in Core.BSF's StartTurn() or StartTurnPost(), both of which already have loops iterating over all units. The previous AI's patch instead bolted it onto RestoreUnitData() — which only fires when a unit changes type (dismounting, war wagons etc.) — meaning LOS is essentially never systematically reset.
There is also a key discovery in Core.BSF: GetTurn() == -1 is the deployment/pre-game phase. Turn 0 is the first actual turn. This means we can use GetTurn() != -1 as a reliable deployment guard instead of IsDeploying(), and it's already used this way throughout Core.BSF.
The Correct Fix
The ApplyLOSAllUnits() call needs to move out of RestoreUnitData() and into StartTurnPost() in Core.BSF, where there is already a loop over all units on both sides. It should be guarded by GetTurn() != -1 to exclude the deployment phase:
bsfFUNCTION StartTurnPost(side)
{
int s;
int i;
int id;
int j;
int enemy;
SetUniversalVar("Side0Remaining", DisplayPercentRemaining(0));
SetUniversalVar("Side1Remaining", DisplayPercentRemaining(1));
if (GetTurn() == -1)
{
// ... existing deployment phase code ...
}
// MODDED: Apply LOS limits to all units at the start of every turn (not during deployment)
if (GetTurn() != -1)
{
for (s = 0; s < 2; s++)
{
for (i = 0; i < GetUnitCount(s); i++)
{
id = GetUnitID(s, i);
if (id != -1)
{
ResetViewDistance(id);
}
}
}
}
// ... rest of existing code ...
And ResetViewDistance itself simplifies back to its clean original logic — no IsDeploying() guard needed since Core.BSF now handles that:
bsfFUNCTION ResetViewDistance(me)
{
if (IsMultiplayer() == 1 || IsAITurn() == 0) // MODDED: HUMAN PLAYERS
{
SetAttrib(me, "LOS", MODDED_LOS);
}
else // MODDED: AI
{
SetAttrib(me, "LOS", MODDED_LOS_AI);
}
SetAttrib(me, "CoverLOS", GetBaseAttrib(me, "CoverLOS"));
}
And the ApplyLOSAllUnits() call should be removed from RestoreUnitData() in TOOLS.BSF — it was never the right place for it.
Shall I produce patched versions of both Core.BSF and TOOLS.BSF (for both Medieval and Ancients) with these changes applied?
Yes, but only small changes to each:So I'd need a patched version of both files?
TOOLS.BSF — two changes: revert ResetViewDistance back to the clean original logic (no IsDeploying() guard), and remove the ApplyLOSAllUnits() call from RestoreUnitData() along with the ApplyLOSAllUnits function definition itself since it'll be redundant
Core.BSF — one change: add the unit loop calling ResetViewDistance inside StartTurnPost(), guarded by GetTurn() != -1
Since you have both a Medieval and Ancients version of TOOLS.BSF, you'd need two versions of that file. But Core.BSF — is that shared between both games, or do you have a separate copy for each? That would determine whether you need one or two patched Core.BSF files.
That makes sense given Medieval has things like fortifications that Ancients doesn't. That makes sense given Medieval has things like fortifications that Ancients doesn't. So the full set of files you'll need patched is:I'm pretty sure medieval core is different as the game has some different features.
Medieval TOOLS.BSF — revert ResetViewDistance + remove ApplyLOSAllUnits from RestoreUnitData()
Ancients TOOLS.BSF — same changes
Medieval Core.BSF — add the LOS loop in StartTurnPost()
Ancients Core.BSF — same change, but applied to a different base file
You've already shared the Medieval Core.BSF. Could you upload the Ancients Core.BSF as well? Then I can produce all four patched files in one go.
Looking back at the uploads, you're right that you shared a Core.Looking back at the uploads, you're right that you shared a Core.BSF — but I can't be certain whether it's the Medieval or Ancients version since it came in the same batch as the Ancients TOOLS.BSF and AITools.bsf.I shared the ancient core, I think
Could you confirm which game that Core.BSF belongs to? And if it's the Ancients one, could you share the Medieval Core.BSF too? Then I'll have all four files I need to produce the complete set of patches.
Perfect, so I still need the Medieval Core.BSF. Could you upload that and then I'll produce all four patched files at once?YIt's ancient
Good — the Medieval StartTurnPost is structurally the same as Ancients but with some extra Medieval-specific code (fortifications, army centres, banner indices. Good — the Medieval StartTurnPost is structurally the same as Ancients but with some extra Medieval-specific code (fortifications, army centres, banner indices). The insertion point for the LOS loop is identical in both. I have everything I need now — let me produce all four files. Check what files are currently available in uploads. Check what files are currently available in uploads. The uploads folder only holds the most recent files — the Ancients Core.BSF has been overwritten by the Medieval one. I'll need you to re-upload the Ancients Core.BSF one more time so I can patch it separately.Here's medieval Core.BSF
In the meantime, I can go ahead and produce the three files I do have everything for:
Medieval TOOLS.BSF ✓
Ancients TOOLS.BSF ✓
Medieval Core.BSF ✓
Could you re-upload the Ancients Core.BSF?
Good, now I have everything. Let me produce all four files. Good — the Medieval V2 file doesn't have ApplyLOSAllUnits at all (that was only in the broken patched file). Now let me fix ResetViewDistance in both TOOLS files, then add the LOS loop to both Core files. Revert ResetViewDistance in Medieval TOOLS to clean logic without IsDeploying guard. Revert ResetViewDistance in Medieval TOOLS to clean logic without IsDeploying guard. Now fix the Ancients TOOLS — same change but from the unmodified original: Now add the LOS loop to Medieval Core.BSF: Add ResetViewDistance loop to Medieval Core.BSF StartTurnPost after the MeleeDue loop. Add ResetViewDistance loop to Medieval Core.BSF StartTurnPost after the MeleeDue loop. Now the same for Ancients Core.BSF: Add ResetViewDistance loop to Ancients Core.BSF StartTurnPost after the MeleeDue loop. Add ResetViewDistance loop to Ancients Core.BSF StartTurnPost after the MeleeDue loop. Now verify all four files with diffs: Verify all four diffs are correct and minimal. Verify all four diffs are correct and minimal. All four diffs look exactly right. Now copy to outputs: All four files are ready.Ancients Core.BSF
Here's a summary of what changed in each:
TOOLS_Medieval_patched.BSF and TOOLS_Ancients_patched.BSF
ResetViewDistance reverted to clean logic — no IsDeploying() guard, since deployment is now handled properly in Core.BSF
Comment updated to explain where the deployment guard lives
Indentation tidied up
Core_Medieval_patched.BSF and Core_Ancients_patched.BSF
A new LOS loop added at the end of StartTurnPost(), calling ResetViewDistance on every unit on both sides, guarded by GetTurn() != -1 to skip the deployment phase
This is the correct hook — it runs reliably every turn, for every unit, in both single player and multiplayer
Nothing else was touched in any of the four files.
--------------------------------------------------
SP works fine in my testing, both SP and hotseat mode. I've solicited a few folks to help test MP before releasing V4.
Regards,
Karvon


