Boudicca's Rebellion - Creation Thread

This sounds awesome! This supersedes but does not replace right? So I don't need to redo all events in any project I'm working on, but can use this going forward for new events?
 
This sounds awesome! This supersedes but does not replace right? So I don't need to redo all events in any project I'm working on, but can use this going forward for new events?

Correct. Any call to civlua.createUnit will work exactly as it has always done. This will give you the option (once I return the updated general library) to use gen.createUnit instead, just as you can also use the more 'raw' civ.createUnit.

I try my best not to make changes that will break stuff that has come before.

I've been bouncing around a bit today with the events. I realized the mistake I made in the Legacy Events for diplomacy, and also made it so that the game will automatically detect when a change has been made to the legacy events, and offer to clear the legacy events state table.
 
Lots of fixes.

Unit promotions to different units appears to work properly (I didn't test every last thing), with a sample Evocati promotion in place.

Guaranteed unit activation looks like it works now (part of eventTools). Currently, arrows are created as the unit that guarantees that an on activation trigger will take place, and these are created on the north eastern edge of the map. These settings can be changed in parameters.lua.

I added a line to setVendetta when the Iceni declare war, hopefully this will get them to attack. War, peace, and contact statuses are set symmetrically by the diplomacy module, so I removed redundant lines. I still haven't gone through and fixed all the globals, but that shouldn't matter for Boudicca. We may want to disable the use of the gifting menu in the key press file, since it isn't relevant to this scenario.

Some legacy event fixes, including that forbidding diplomacy now recognizes the 'anybody' keyword. I've introduced code to detect changes in either the importedEvents.txt or the Legacy Event Engine, and prompt the player to reset the legacy event state to take advantage of fixes and avoid bugs resulting from changing events. (This eliminates active flags, active trigger conditions, justOnce settings, and similar stuff like that.) When you load your workon save, accept clearing the legacy events; this will get the forbidden negotiation to work correctly.

I added a unitDeathOutsideCombat function to the trigger events, in case it is needed.

I re-wrote the Cerialis killed event to take advantage of gen.createUnit (especially 'scatter' option), and gen.nearbyOpenTilesForTribe (similar to nearby empty tiles, but it also catches tiles defended by the particular tribe). I re-wrote the general Roman unit killed event, but did so before I wrote the aforementioned functions, so you may want to re-write it again. I didn't look at the other unit killed events.

I think this is all for now.
 

Attachments

  • BoudiccaCode.zip
    576.1 KB · Views: 65
Just because I'm building a second thing concurrently - would you mind letting me know what core files you changed (I assume I'd not need to make any changes within files that aren't core for my purposes). I'm guessing the general library, diplomacy, eventTools?
 
I'm not entirely sure, I was making a lot of changes, figuring that I could just return the entire lua core. Is there a reason you can't just replace the existing files with the new ones?

According to my pc's modified dates, these were changed:
diplomacy, flag, counter, promotion, eventTools, legacyEventBuilder, legacyEventEngine, generalLibrary, text, and you'll also need (if you use legacy events) secureHashAlgorithm.

You will also need to update events.lua.

I think you're fine with the non-core files. If I made any fixes, they were trivial things like spelling.
 
I am not making any changes to my core files but I guess I was worried that some of the non-core ones also had changes - I'm making a point not to mess around with anything core so that (hopefully) things remain compatible but I just wanted to double check. Thanks!
 
I tested delayed events. Here are some fixes, including to events.lua.
 

Attachments

  • BoudiccaEvents-11-Dec.zip
    6.7 KB · Views: 71
Here's an update to the Lua Core. I think I've found all the Globals in diplomacy.lua.
 

Attachments

  • LuaCoreUpdate.zip
    30.8 KB · Views: 63
Ok cool - really sorry about the delays on my end. We got the internet working to an extent but not on my machine (it always had an issue with its onboard wifi adapter, and the new adapter I purchased is, for some reason, blowing up the internet for the rest of the house). I have a call into an electrician to snake an ethernet cable to my office, but until that happens I have to basically toss it over the railing and create a giant tripping hazard meaning I don't have much access except very early. It will cause some delays.
 
That's weird that the wifi adapter would cause trouble for the rest of the network. I'd probably go to an electronics store with a good return policy and try another adapter before calling in an electrician. You might also try something like this https://www.newegg.ca/tp-link-tl-wp...cm_re=network_extender-_-33-704-197-_-Product . (I haven't used one, so I can't actually vouch for this solution.) In the meantime, you might be able to use 'USB Tethering' (I'm 99% sure that's what it is called, haven't done in in a while) with your phone, to access your wifi through your phone. (This is different from creating a wifi hotspot, which would let you access your cell data.) Good luck.
 
Honestly I've always had an ethernet connection and the flight sims I play demand it, so its really the desirable solution at this stage. I have all the parts and am hopeful this will be a job that doesn't take the electrician more than an hour. The schematic of the house is such I think I could handle it, but I dont have the right tools and am afraid of inadvertently doing some damage.
 
OK I finally have a little bit of internet so while OTR is stewing I'm trying to get back to work on this...

I'm not quite sure I understand the delay mechanism just yet but I think that's one thing we'll definitely want an example for. How would I add a delay of, say, 3 turns to the Caledonii Revolt happening?

Code:
if turn >= 1  and enoughCohortsInScotland()  then 
    for unit in civ.iterateUnits() do 
        if unit.owner == object.tCaledonii then
            unit.moveSpent = unit.type.move
        end
    end 
else 
gen.justOnce("Caledonii Revolt",function ()
civ.ui.text("The withdrawal of our legions from northern Britain has emboldened the Caledonii! They flood across our borders and add to the carnage facing us!")
diplomacy.setWar(object.tRomans,object.tCaledonii)
    for unit in civ.iterateUnits() do
        if unit.owner == object.tCaledonii then
        unit.gotoTile = civ.getTile(43,95,0)
        end 
    end 
end) 
end

Also, is this the right way to ensure that a unit will automatically kill another? It seems to be working but I wanted to double check:

Code:
function combatResolution.firstRound(defaultResolutionFunction,defender,attacker)

if attacker.type == object.uDispatchRider and defender.type == object.uLegionStandard  then
return false 

else 


    return true
end

What would I need to do if I wanted to make it so that a particular unit would do better against other units (say, automatically do at least 5 damage?) while I don't really "need" this for Boudicca I could see it being very useful elsewhere.

end
 
Also, is this the right way to ensure that a unit will automatically kill another? It seems to be working but I wanted to double check:
Spoiler :
If not mistaken, you should civ.deleteUnit(attacker or defender) before returning false, as not to have any surprise ?
Also, shouldn't you rather return as default situation "defaultResolutionFunction(attacker, defender)" ?
<= with thanks to @Prof. Garfield , it indeed generates errors ! To stay hidden !

Guess Prof. or another will manage o make it clear.
What would I need to do if I wanted to make it so that a particular unit would do better against other units (say, automatically do at least 5 damage?)
You could have there in this resolutionfunction many systems :
-you could have a unit (say bowman) launch simulated free attacks before going on with the fight,
-you could indeed inflict free damages
-you could check if a medic unit is around to heal from damage,
One could imagine an alea for AI or dialog for human players to allow "reddition" under conditions (like say check if all tiles 1 tile around have as tile.defender the attacker tribe)...
-for exemple, i used this place in incoming scenario to set the AntiAir Reaction as well as the Artillery Defensive Support reaction, also setting up the Units special bonuses (anti tank gunsagainst armors, infantries against infantries or armors considering technologies, armored cars against infantries ...)
-and many other uses may rise from awsome creative people of your kind :)

all these and for sure many more, once or before any round
 
Last edited:
Also, is this the right way to ensure that a unit will automatically kill another? It seems to be working but I wanted to double check:

You should set the hp of the unit you wish to kill to 0 (or less). That is, if you want the defender to lose, set
Code:
defender.damage = defender.type.hitpoints
before you return false

What would I need to do if I wanted to make it so that a particular unit would do better against other units (say, automatically do at least 5 damage?) while I don't really "need" this for Boudicca I could see it being very useful elsewhere.

In the combatResolution.firstRound function, check if certain conditions apply. If so, add some damage.

Suppose we have 2 functions, one that determines if a unit type is a 'tank', and another that determines if a square has an anti-tank gun on it, regardless of whether the defender is the anti-tank gun. If both conditions are met, we want to do 5 damage to the tank before anything else happens. Thus,
Code:
function combatResolution.firstRound(defaultResolutionFunction,defender,attacker)
if isTank(attacker.type) and hasAntiTankGun?(defender.location) then
    attacker.damage = attacker.damage+5
end

return true
end

If the attacker's damage exceeds its hitpoints due to this event, then the 'every round' function will 'catch' that and return false before combat starts.

If not mistaken, you should civ.deleteUnit(attacker or defender) before returning false, as not to have any surprise ?
I wouldn't use deleteUnit within the combat resolution event. I haven't tried it, but I suspect that nothing good can come of it. To ensure that the correct unit dies, set that unit's damage so that it the unit has at most 0 hp. (And, ensure the 'surviving' unit has at least 1 hp.)

Also, shouldn't you rather return as default situation "defaultResolutionFunction(attacker, defender)" ?
In the template, there are 2 functions. One that activates only on the first round of combat, and one that activates during every round of combat, including the first. If both those functions return true on the first round of combat, then combat proceeds. For subsequent rounds, the 'first round of combat' function is not checked. The everyRound function is the one that returns defaultResolutionFunction(defender,attacker) by default.

This separation serves two purposes. The first is that there are certain things that it only makes sense to check once (such as the anti tank gun example I gave above). The second is that even if no harm is done doing certain things over and over again, the checks might slow down combat. For example, in OTR, we have a lot of automatic loss checks, and it is possible (though I haven't checked) that a noticeable delay might occur if they happened every round of combat.

I'm not quite sure I understand the delay mechanism just yet but I think that's one thing we'll definitely want an example for. How would I add a delay of, say, 3 turns to the Caledonii Revolt happening?
 
Oops, forgot to answer this. Disclaimer: none of this was tested, so let me know if you run into trouble.

I'm not quite sure I understand the delay mechanism just yet but I think that's one thing we'll definitely want an example for. How would I add a delay of, say, 3 turns to the Caledonii Revolt happening?

Code:
-- This portion of code can go anywhere outside of a function
local function startCaledoniiRevolt(argTable)
civ.ui.text("The withdrawal of our legions from northern Britain has emboldened the Caledonii! They flood across our borders and add to the carnage facing us!")
diplomacy.setWar(object.tRomans,object.tCaledonii)
   for unit in civ.iterateUnits() do
       if unit.owner == object.tCaledonii then
       unit.gotoTile = civ.getTile(43,95,0)
       end
   end
end

delayedAction.makeFunctionDelayable("beginCaledoniiRevolt",startCaledoniiRevolt)

-- this portion of code should go where the trigger happens
gen.justOnce("CaledoniiRevolt",function()
    delayedAction.doInFuture("beginCaledoniiRevolt",{},civ.getTurn()+3,object.tCaledonii.id)
end)

You will need more complicated code if you wish to have the rebellion cancelled if the Legions return before the 3 turns are elapsed.

Here's a delayed create unit example

Code:
local function postponedCreateUnit(argTable)
    civ.createUnit(civ.getUnitType(argTable.unitTypeID),civ.getTribe(argTable.tribeID),gen.getTileFromID(argTable.tileID))
end
delayedAction.makeFunctionDelayable("postponedUnitCreation")

-- this will create units for a tribe at a future turn, during that tribe's after production phase
local function createUnitInFuture(unitType,tribe,location,turnDelay)
    delayedAction.doInFuture("postponedUnitCreation",{unitTypeID=unitType.id,tribeID=tribe.id,tileID=gen.getTileID(location)},civ.getTurn()+turnDelay,tribe.id)
end
 
Thanks!

I playtested the scenario a few times today and cleaned some stuff up. When I have my "internet time" tomorrow I'll upload what I have so far, but the scenario is playable and works as intended. Now it's just a matter of figuring out what other examples we want to have and adding them.
 
Suppose we have 2 functions, one that determines if a unit type is a 'tank', and another that determines if a square has an anti-tank gun on it, regardless of whether the defender is the anti-tank gun. If both conditions are met, we want to do 5 damage to the tank before anything else happens. Thus,

This would require both isTank and hasAntiTankGun? to have a table right?

isTank = { }

isTank[object.uPanzerIV.id]

and

hasAntiTankGun? = { }

hasAntiTankGun?[object.u88mmFlak.id]
 
This would require both isTank and hasAntiTankGun? to have a table right?

isTank = { }

isTank[object.uPanzerIV.id]

and

hasAntiTankGun? = { }

hasAntiTankGun?[object.u88mmFlak.id]

For isTank, you could use a table in place of a function, that is

Code:
if isTank[attacker.type.id] and
The way I described it, however, isTank was a function

Code:
local tankTable = {}
tankTable[object.uPanzerIV.id]=true

local function isTank(unit)
return tankTable[unit.type.id]
end
However, there might be other ways to define a tank. Maybe all tanks have 2 mp and treat all squares as roads. In that case, you might check for those conditions instead of explicitly writing a table. Or, maybe, you might define an anti-tank gun as a unit that can fire a certain kind of munition, and leverage the munition tables to check the definition instead. Perhaps all the tanks are in the same place in the unit list, so you could just check if the id is within a certain range.

As described, however, hasAntiTankGun must be a function, since I'm not just checking if the defender is an anti tank gun, but rather if there is one on that square. In that case, using a table for a definition,
Code:
local antiTankTable = {}
antiTankTable[object.u88mmFlak.id]=true

local function hasAntiTankGun(tile)
for unit in tile.units do
    if antiTankTable[unit.type.id] then
        return true
    end
end
return false
end
 
Here's the scenario files - note there's still a lot of clutter (old testing saves etc.) but if anyone would like to play the scenario they can feel free. Note that there isn't much of a readme yet so a few things:

-Your legionaries fire 2x pila per turn (press k)
-You'll need to bring Suetonius at least as far east as x68 to allow him to retreat to Watling Street.
-By pressing 'u', Suetonius can send out dispatch riders to pick up troops some little Eagle standards marked on the map (roughly near Exeter and Gloucester).

This is a very quick, hard-hitting scenario so you may well be able to playtest a match of it in 1-2 hours. If anyone has some time this weekend that would be appreciated.
 

Attachments

  • Boudicca.zip
    23.4 MB · Views: 62
One more thing - I believe an event will tell you to press "3" when you want to make your last stand on Watling St. But it's actually "k". I need to change that.
 
Top Bottom