Okay, so I tested EEex_ReadLString(EEex_GetActorShare(actorID) + 0x3596, 8) on an enemy while it was doing a SpellRES action, and it didn't get the RES. It only seems to work for party members.
@Bubb Where or how is the info stored about the spell an actor is casting with SpellRES?
@OlvynChuru: This grabs the first string arg of the currently executing action:
function B3PrintStringArg()
local actorID = EEex_GetActorIDCursor()
if actorID == 0x0 then return end
local actionStringArg = EEex_ReadLString(EEex_ReadDword(EEex_GetActorShare(actorID) + 0x344), 8)
Infinity_DisplayString("Current String: "..actionStringArg)
end
I don't believe the engine stores the current spell cast anywhere else. The action-overlay shown on party portraits uses a combination of checking the current action ID and the first string arg, so I assume that's the "correct" way to do it.
Also, it appears the engine always fills the string arg, even when using the straight ID version of the spell actions, so I believe it is safe to directly poll the spell that way.
This is already possible. The problem with Black Blade of Disaster is that effects with a higher timing override effects of the same opcode with a lower timing. Thus, the character's normal long sword proficiency effect (with timing = 9) overrides the Black Blade of Disaster equipped proficiency effect (with timing = 2). However, if you give the Black Blade of Disaster spell a proficiency effect granting 5 stats in long swords with timing = 10 (instant, limited, in ticks) with a duration 15 times the normal duration of the spell (15 ticks per second), it will grant the proficiency correctly.
A normal second-based duration works as well. Either way you retain that higher proficiency if you change weapons until the spell expires, which still isn't correct.
But you cannot change your weapon if you're currently equipped with a magically created weapon (opcode #111).... So what's the problem with this workaround?
But you cannot change your weapon if you're currently equipped with a magically created weapon (opcode #111).... So what's the problem with this workaround?
Sure you can, just cast a one-use item creation spell, like shocking grasp.
I fixed the EEex_GetActorSpellRES function. It now gets the spell RES regardless of whether the spell was cast manually or by a script, it works with both Spell and SpellRES - type actions correctly, and it only works if the actor is currently casting a spell or about to cast a spell (it returns "" otherwise).
@OlvynChuru: I was thinking more general, as in it screens every effect applied to the target, but yes.
The function would receive the creature data / actorID of the target creature + the data of the effect that is being screened. Return value of true would block the effect from applying, and false would, of course, allow its application.
Since the whole opcode immunity / type immunity systems are so hardcoded I was wondering if lending the flexibility of a Lua function would do anything interesting.
Just want to say that I'm super happy to see this development ( @OlvynChuru adding to the project). This is just awesome and really appreciated.
Also, I'm a bit behind on the updates. I seem to recall that there was an opcode in the works that would execute a script directly. I think @Bubb said that it was so easy to do that it was surprising that there wasn't already an opcode for this. Has this been completed?
@Grammarsalad: Funny you would ask that; I was literally looking into that early today.
I believe OlvynChuru first asked me if we could have a custom Opcode #298, which instead of being hardcoded to execute cut250a.bcs, could be used to execute any script of our choosing.
I guess it depends on the goal of the script. Opcode #298 is actually much more complex than it first seems, it has several tasks:
1) It stores party locations for use in returning from the pocket plane.
2) Puts the engine into "cutscene" mode.
3) Forces the protagonist to execute an uninterruptible script action 'StartCutScene("cut250a")'
If the goal is simply starting a cutscene from an Opcode, I suppose it would be easy to rig Opcode #298 to allow custom scripts.
The main problem with the "arbitrary script execution" idea is that script actions have to be executed in the context of a game object. Whether this be the area itself, or forcibly put upon a creature, (as in what cutscenes do with CutSceneId()). There's a lot to unpack here when I expected it to be simple - I'll see what I can come up with
PS: If these semi-rants get a little carried away, it's because they help me organize my thoughts on a problem.
@Grammarsalad: Funny you would ask that; I was literally looking into that early today.
I believe OlvynChuru first asked me if we could have a custom Opcode #298, which instead of being hardcoded to execute cut250a.bcs, could be used to execute any script of our choosing.
I guess it depends on the goal of the script. Opcode #298 is actually much more complex than it first seems, it has several tasks:
1) It stores party locations for use in returning from the pocket plane.
2) Puts the engine into "cutscene" mode.
3) Forces the protagonist to execute an uninterruptible script action 'StartCutScene("cut250a")'
If the goal is simply starting a cutscene from an Opcode, I suppose it would be easy to rig Opcode #298 to allow custom scripts.
The main problem with the "arbitrary script execution" idea is that script actions have to be executed in the context of a game object. Whether this be the area itself, or forcibly put upon a creature, (as in what cutscenes do with CutSceneId()). There's a lot to unpack here when I expected it to be simple - I'll see what I can come up with
PS: If these semi-rants get a little carried away, it's because they help me organize my thoughts on a problem.
I was thinking of something more like 3--that is, just forcing some x to execute a script--but maybe:
3a) Execute any arbitrary script (perhaps named in the Resource key)
3b) Rather than the Protagonist having to execute the script action, perhaps with targeting taken from object.ids according to the value in a parameter or special? That is, the targeted object will execute the script?
I don't know if it would be better as a new opcode or a modification of #298...
I think it'll work best as a new opcode, without having it to do anything aside from telling the engine to start running a script using the target of the opcode as the active creature that will continue the task.
I don't think filtering the target against an IDS is a good idea. We already have things that do that and they work well on their own. It'd be unnecessary complexity in my opinion.
And as for the progress on the Mac version, I've been lagging on actual progress but I've done a lot of thinking on it, and I'm rather close to achieving something to run Lua scripts via the game's engine in the game's own memory space, while being able to load and use all of the game's own internal C/C++ functions. I've made no progress in being able to edit those functions (no reliable/secure way of hooking), so at this point I'm just sitting on a half-theoretical improved console.
EEex_GetActorOverrideScript(actorID) => Returns the resref of the actor's override script.
EEex_GetActorSpecificsScript(actorID) => Returns the resref of the actor's specifics script.
EEex_GetActorClassScript(actorID) => Returns the resref of the actor's class script.
EEex_GetActorRaceScript(actorID) => Returns the resref of the actor's race script.
EEex_GetActorGeneralScript(actorID) => Returns the resref of the actor's general script.
EEex_GetActorDefaultScript(actorID) => Returns the resref of the actor's default script.
Also, I fixed a bug with EEex_GetActorAreaRes(actorID). That function previously would crash if it was called right when a game was loaded, because the actor didn't yet get assigned a pointer to the area. I made it so if that hasn't happened yet, it'll return "".
By the way, that idea for an opcode that runs a Lua function each time an effect would be applied sounds really useful! One idea I have for it is to use it to "link" creatures so that any effect that's applied to one will be applied to the other as well (to avoid an infinite loop, it would set a bit on that copied effect and only copy effects that don't have that bit set).
How would one determine with a Lua function whether an actor is currently moving to access a door, container, trap, or area transition? Where in the actor data is that stored?
Is it possible to make an enemy controllable without charming it or changing its allegiance? I had an idea of changing Command so it actually lets you give the target a command (rather than just putting it to sleep). Where in the actor data does it store whether or not the actor is controllable? There seems to be something other than a creature's EA value that determines whether or not it can be controlled.
How would one determine with a Lua function whether an actor is currently moving to access a door, container, trap, or area transition? Where in the actor data is that stored?
It appears it isn't put into a special field. It's part of the action queue, (when you click to do one of those actions the engine lines up a MoveToPoint and the correct action). The only way to detect it after the fact would be to detect the correct sequence of actions in the queue. Also, it might be hard to distinguish certain actions if the "key" action can be executed by script, (like LeaveArea() in the case of an area transition).
Is it possible to make an enemy controllable without charming it or changing its allegiance? I had an idea of changing Command so it actually lets you give the target a command (rather than just putting it to sleep). Where in the actor data does it store whether or not the actor is controllable? There seems to be something other than a creature's EA value that determines whether or not it can be controlled.
There are master lists in the engine that control which control state the given creature is in. The categories are:
1) Allies (green circle, controllable, limited functionality)
2) Familiars (green circle, controllable, +area transition functionality)
3) Overflow (limbo state for character-arbitration, I believe)
4) Party (every permission under the sun)
Now, don't get excited thinking you can shove any ol' creature into the "Party" slot. Party members have a lot of hardcoded mechanics to them that limits the total amount possible to 6, sadly.
I'll post some code that works with these mechanisms tomorrow.
EEex_HasState(actorID, state) => Returns true if the actor has the specified state. For example, EEex_HasState(EEex_GetActorIDCursor(), 0x8000) would return true if the actor was hasted (because STATE_HASTE is 0x8000 in STATES.IDS).
EEex_GetActorMovementRate(actorID, adjustForHaste) => Gets the actor's movement rate. If adjustForHaste is true, it returns double the number if the actor is hasted and half if the actor is slowed. For example, this would return 0 if the actor was entangled, and it would return double the value if the actor had Boots of Speed equipped. Both opcodes 126 and 176 affect the value this returns. If the actor doesn't have an effect that changes its movement speed, this returns the "move_scale" value from the actor's animation INI file.
EEex_IsImmuneToOpcode(actorID, opcode) => Returns true if the actor is immune to the specified opcode. For example, EEex_IsImmuneToOpcode(EEex_GetActorIDCursor(), 128) would return true if the actor is immune to confusion (opcode 128).
EEex_IsImmuneToSpellLevel(actorID, level, includeSpellDeflection) => Returns true if the actor is protected from the specified spell level (e.g. by Minor Globe of Invulnerability). If includeSpellDeflection is true, it also returns true if the actor has Spell Deflection, Spell Turning, or Spell Trap for the specified level.
EEex_GetSummonerID(actorID) => If the actor is a summoned creature, this gets the actor ID of the creature's summoner. If it's not a summoned creature, or if it's an image (created by Mislead, Project Image or Simulacrum), it returns 0. It also returns 0 if the creature was summoned before the game was loaded, unfortunately.
EEex_GetImageMasterID(actorID) => If the actor is an image, this gets the actor ID of the image's master. Otherwise, it returns 0. This function works correctly even if the image was created before the game was loaded.
Also, I fixed EEex_AlterActorEffect and EEex_IterateActorEffects so they don't use global variables (this had caused a crash if you called one of them inside another one).
@OlvynChuru: Looks good; merged! Regarding the puppet-master ID, I believe you can also get this by reading Stat #138 using EEex_GetActorStat().
EEex_GetImageMasterID(actorID) also lets you get the actor ID of the image's master even if the image doesn't have an opcode 237 effect on it. There are some instances when I actually want to remove that effect: removing that effect from a Mislead image stops Mislead from granting the master permanent invisibility even after attacking. Even in such a case, this function still gets the master ID.
@OlvynChuru: You're right, of course; that's what I get for not reading the two lines of documentation literally right about the function where you explain that...
Is it imperative that the EA value of the target doesn't get changed with your control request? It appears the engine has an internal "EA_CONTROLCUTOFF = 15" which is another validation layer on which creatures should be controllable. Any creature with an EA <= 15 + added to the allies master list can be ordered by the player, but I don't think you want the EA to change...
I can hook into the EA check and override it in certain circumstances, if need be.
@Bubb It's okay, you don't need to go that far. If necessary, I could simply have Command give a 1-round charm effect or something, rather than that. I'm more interested in that opcode you proposed that screens each effect that would be applied to a creature. I'm really excited to start messing around with that!
Was thinking about that Opcode last night and thought of something: Should the screening process take place before or after other effect-immunity mechanisms? Should it "see" an effect that would otherwise be blocked by level, type, etc.?
I think it should see the effect before other effect-immunity mechanisms. That way, it's still possible to replicate the other way (by immediately returning false if the actor is immune to the effect). Whereas if it comes after other effect-immunity mechanisms, it's not possible to replicate the first way.
Was thinking about that Opcode last night and thought of something: Should the screening process take place before or after other effect-immunity mechanisms? Should it "see" an effect that would otherwise be blocked by level, type, etc.?
Something to keep in mind when adding this: https://support.baldursgate.com/issues/33047
It was fixed in v2.5, but just in case it becomes relevant when adding the new immunity mechanic.
@Bubb I have a problem. It seems like my EEex_IterateActorEffects() function doesn't catch equipped effects of items. Are those stored somewhere different from an actor's other effects? How could I go through all of them?
I've just pushed some code that allows CStringList to be used as part of creature stats... (going to use this in the upcoming Screening Opcode). The changes made could have the potential to cause problems, so just a heads up to look for any new crashes / make sure the extended stats system still is working as expected.
Edit: Looks like I left a direct-address reference in. The master branch might only work with BG2:EE until I fix that tomorrow; sorry! If you need to download from master in the meantime, use this link: here.
Comments
@Bubb Where or how is the info stored about the spell an actor is casting with SpellRES?
I don't believe the engine stores the current spell cast anywhere else. The action-overlay shown on party portraits uses a combination of checking the current action ID and the first string arg, so I assume that's the "correct" way to do it.
Also, it appears the engine always fills the string arg, even when using the straight ID version of the spell actions, so I believe it is safe to directly poll the spell that way.
But you cannot change your weapon if you're currently equipped with a magically created weapon (opcode #111).... So what's the problem with this workaround?
I fixed the EEex_GetActorSpellRES function. It now gets the spell RES regardless of whether the spell was cast manually or by a script, it works with both Spell and SpellRES - type actions correctly, and it only works if the actor is currently casting a spell or about to cast a spell (it returns "" otherwise).
Basically an Opcode #101 that would be evaluated on every effect application based on the function's return value.
What would be passed to the Lua function?
The function would receive the creature data / actorID of the target creature + the data of the effect that is being screened. Return value of true would block the effect from applying, and false would, of course, allow its application.
Since the whole opcode immunity / type immunity systems are so hardcoded I was wondering if lending the flexibility of a Lua function would do anything interesting.
Also, I'm a bit behind on the updates. I seem to recall that there was an opcode in the works that would execute a script directly. I think @Bubb said that it was so easy to do that it was surprising that there wasn't already an opcode for this. Has this been completed?
I believe OlvynChuru first asked me if we could have a custom Opcode #298, which instead of being hardcoded to execute cut250a.bcs, could be used to execute any script of our choosing.
I guess it depends on the goal of the script. Opcode #298 is actually much more complex than it first seems, it has several tasks:
1) It stores party locations for use in returning from the pocket plane.
2) Puts the engine into "cutscene" mode.
3) Forces the protagonist to execute an uninterruptible script action 'StartCutScene("cut250a")'
If the goal is simply starting a cutscene from an Opcode, I suppose it would be easy to rig Opcode #298 to allow custom scripts.
The main problem with the "arbitrary script execution" idea is that script actions have to be executed in the context of a game object. Whether this be the area itself, or forcibly put upon a creature, (as in what cutscenes do with CutSceneId()). There's a lot to unpack here when I expected it to be simple - I'll see what I can come up with
PS: If these semi-rants get a little carried away, it's because they help me organize my thoughts on a problem.
I was thinking of something more like 3--that is, just forcing some x to execute a script--but maybe:
3a) Execute any arbitrary script (perhaps named in the Resource key)
3b) Rather than the Protagonist having to execute the script action, perhaps with targeting taken from object.ids according to the value in a parameter or special? That is, the targeted object will execute the script?
I don't know if it would be better as a new opcode or a modification of #298...
I don't think filtering the target against an IDS is a good idea. We already have things that do that and they work well on their own. It'd be unnecessary complexity in my opinion.
And as for the progress on the Mac version, I've been lagging on actual progress but I've done a lot of thinking on it, and I'm rather close to achieving something to run Lua scripts via the game's engine in the game's own memory space, while being able to load and use all of the game's own internal C/C++ functions. I've made no progress in being able to edit those functions (no reliable/secure way of hooking), so at this point I'm just sitting on a half-theoretical improved console.
Six new functions:
EEex_GetActorOverrideScript(actorID) => Returns the resref of the actor's override script.
EEex_GetActorSpecificsScript(actorID) => Returns the resref of the actor's specifics script.
EEex_GetActorClassScript(actorID) => Returns the resref of the actor's class script.
EEex_GetActorRaceScript(actorID) => Returns the resref of the actor's race script.
EEex_GetActorGeneralScript(actorID) => Returns the resref of the actor's general script.
EEex_GetActorDefaultScript(actorID) => Returns the resref of the actor's default script.
Also, I fixed a bug with EEex_GetActorAreaRes(actorID). That function previously would crash if it was called right when a game was loaded, because the actor didn't yet get assigned a pointer to the area. I made it so if that hasn't happened yet, it'll return "".
How would one determine with a Lua function whether an actor is currently moving to access a door, container, trap, or area transition? Where in the actor data is that stored?
Is it possible to make an enemy controllable without charming it or changing its allegiance? I had an idea of changing Command so it actually lets you give the target a command (rather than just putting it to sleep). Where in the actor data does it store whether or not the actor is controllable? There seems to be something other than a creature's EA value that determines whether or not it can be controlled.
It appears it isn't put into a special field. It's part of the action queue, (when you click to do one of those actions the engine lines up a MoveToPoint and the correct action). The only way to detect it after the fact would be to detect the correct sequence of actions in the queue. Also, it might be hard to distinguish certain actions if the "key" action can be executed by script, (like LeaveArea() in the case of an area transition).
There are master lists in the engine that control which control state the given creature is in. The categories are:
1) Allies (green circle, controllable, limited functionality)
2) Familiars (green circle, controllable, +area transition functionality)
3) Overflow (limbo state for character-arbitration, I believe)
4) Party (every permission under the sun)
Now, don't get excited thinking you can shove any ol' creature into the "Party" slot. Party members have a lot of hardcoded mechanics to them that limits the total amount possible to 6, sadly.
I'll post some code that works with these mechanisms tomorrow.
Six new functions:
EEex_HasState(actorID, state) => Returns true if the actor has the specified state. For example, EEex_HasState(EEex_GetActorIDCursor(), 0x8000) would return true if the actor was hasted (because STATE_HASTE is 0x8000 in STATES.IDS).
EEex_GetActorMovementRate(actorID, adjustForHaste) => Gets the actor's movement rate. If adjustForHaste is true, it returns double the number if the actor is hasted and half if the actor is slowed. For example, this would return 0 if the actor was entangled, and it would return double the value if the actor had Boots of Speed equipped. Both opcodes 126 and 176 affect the value this returns. If the actor doesn't have an effect that changes its movement speed, this returns the "move_scale" value from the actor's animation INI file.
EEex_IsImmuneToOpcode(actorID, opcode) => Returns true if the actor is immune to the specified opcode. For example, EEex_IsImmuneToOpcode(EEex_GetActorIDCursor(), 128) would return true if the actor is immune to confusion (opcode 128).
EEex_IsImmuneToSpellLevel(actorID, level, includeSpellDeflection) => Returns true if the actor is protected from the specified spell level (e.g. by Minor Globe of Invulnerability). If includeSpellDeflection is true, it also returns true if the actor has Spell Deflection, Spell Turning, or Spell Trap for the specified level.
EEex_GetSummonerID(actorID) => If the actor is a summoned creature, this gets the actor ID of the creature's summoner. If it's not a summoned creature, or if it's an image (created by Mislead, Project Image or Simulacrum), it returns 0. It also returns 0 if the creature was summoned before the game was loaded, unfortunately.
EEex_GetImageMasterID(actorID) => If the actor is an image, this gets the actor ID of the image's master. Otherwise, it returns 0. This function works correctly even if the image was created before the game was loaded.
Also, I fixed EEex_AlterActorEffect and EEex_IterateActorEffects so they don't use global variables (this had caused a crash if you called one of them inside another one).
EEex_GetImageMasterID(actorID) also lets you get the actor ID of the image's master even if the image doesn't have an opcode 237 effect on it. There are some instances when I actually want to remove that effect: removing that effect from a Mislead image stops Mislead from granting the master permanent invisibility even after attacking. Even in such a case, this function still gets the master ID.
Is it imperative that the EA value of the target doesn't get changed with your control request? It appears the engine has an internal "EA_CONTROLCUTOFF = 15" which is another validation layer on which creatures should be controllable. Any creature with an EA <= 15 + added to the allies master list can be ordered by the player, but I don't think you want the EA to change...
I can hook into the EA check and override it in certain circumstances, if need be.
Was thinking about that Opcode last night and thought of something: Should the screening process take place before or after other effect-immunity mechanisms? Should it "see" an effect that would otherwise be blocked by level, type, etc.?
https://support.baldursgate.com/issues/33047
It was fixed in v2.5, but just in case it becomes relevant when adding the new immunity mechanic.
Edit: Looks like I left a direct-address reference in. The master branch might only work with BG2:EE until I fix that tomorrow; sorry! If you need to download from master in the meantime, use this link: here.