Jump to content

Concordance of new features


Ascension64

Recommended Posts

This is what I have in TobEx so far. A couple of additions here were by request.

//wParam1High
#define EFFECTLEARNSPELL_NO_XP_ALWAYS		0x0001 //new in GemRB, TobEx
#define EFFECTLEARNSPELL_PRINT_MESSAGE		0x0002 //reserved for GemRB
#define EFFECTLEARNSPELL_SUCCESS_ALWAYS		0x0004 //new in GemRB, TobEx
#define EFFECTLEARNSPELL_MEMORIZE_NOW		0x0008 //reserved for GemRB

#define EFFECTLEARNSPELL_NO_XP_DUPLICATE	0x0010 //new in TobEx
#define EFFECTLEARNSPELL_RESTRICT_SCHOOL	0x0020 //new in TobEx
#define EFFECTLEARNSPELL_NO_SORCERER		0x0040 //new in TobEx

Link to comment
I wonder how exactly the no sorcerer/restrict school thing works.

Wouldn't it be better to have an unusability flag for them?

Besides, sorcerers don't learn spells from scrolls, do they?

Those flags are only used when learn spell opcode is used outside the scroll environment. Maybe some reversed code will help:

BOOL CEffectLearnSpell::ApplyEffect(CCreatureObject& creTarget) {
IECString sLearnSpellMod;
int nLearnSpellMod;
int nRow = creTarget.cdsPrevious.intelligence;
int nCol = 0; //LEARN_SPELL
if (nCol < g_pChitin->pGame->INTMOD.nCols &&
	nRow < g_pChitin->pGame->INTMOD.nRows &&
	nCol >= 0 &&
	nRow >= 0) {
	sLearnSpellMod = *((g_pChitin->pGame->INTMOD.pDataArray) + (g_pChitin->pGame->INTMOD.nCols * nRow + nCol));
} else {
	sLearnSpellMod = g_pChitin->pGame->INTMOD.defaultVal;
}
sscanf_s((LPCTSTR)sLearnSpellMod, "%d", &nLearnSpellMod);

int nRand = IERand() * 100 >> 15;

if (g_pChitin->cNetwork.bSessionOpen &&
	g_pChitin->pGame->m_GameOptions.m_nMPDifficultyMultiplier < 0) {
	nRand = 1;
} else
if (g_pChitin->pGame->m_GameOptions.m_nDifficultyMultiplier < 0) {
	nRand = 1;
}

ResSplContainer resSpell;
resSpell.LoadResource(effect.rResource, TRUE, TRUE);
if (!resSpell.bLoaded) {
	bPurge = TRUE;
	return TRUE;
}

short wLevel = resSpell.GetSpellLevel();
short wType = resSpell.GetSpellType();

if (g_pChitin->pGame->GetMageSchool(creTarget.m_BaseStats.kit[1]) == resSpell.GetSpellSchoolPrimary()) {
	nLearnSpellMod += 15;
} else {
	nLearnSpellMod -= 15;
}

if (nRand <= nLearnSpellMod) { //success
	switch (wType) {
	case SPELLTYPE_MAGE:
		creTarget.AddKnownSpellMage(effect.rResource, wLevel - 1);
		break;
	case SPELLTYPE_PRIEST:
		creTarget.AddKnownSpellPriest(effect.rResource, wLevel - 1);
		break;
	default:
		creTarget.AddKnownSpell(effect.rResource, FALSE);
		break;
	}

	if (creTarget.GetCurrentObject().EnemyAlly <= EA_CONTROLLEDCUTOFF) {
		IECString sXPBonus;
		int nXPBonus;
		nRow = 2; //LEARN_SPELL
		nCol = wLevel - 1;

		if (nCol < g_pChitin->pGame->XPBONUS.nCols &&
			nRow < g_pChitin->pGame->XPBONUS.nRows &&
			nCol >= 0 &&
			nRow >= 0) {
			sXPBonus = *((g_pChitin->pGame->XPBONUS.pDataArray) + (g_pChitin->pGame->XPBONUS.nCols * nRow + nCol));
		} else {
			sXPBonus = g_pChitin->pGame->XPBONUS.defaultVal;
		}
		sscanf_s((LPCTSTR)sXPBonus, "%d", &nXPBonus);

		g_pChitin->pGame->AddExperienceParty(nXPBonus);
	}
}

resSpell.Unload();
bPurge = TRUE;
return TRUE;
}

 

In the non-scroll context, no restrictions to apply to school or class. We did talk about unusability flags (don't need them for the scroll setting) but I think the bigg (who requested the restrictions) was planning to use such an effect as a one-off application rather than something the user can control through the .itm (in which case the .itm unusability flags are enough).

 

Here is how the TobEx version looks like:

 

BOOL DETOUR_CEffectLearnSpell::DETOUR_ApplyEffect(CCreatureObject& creTarget) {
int wParam1High = effect.nParam1 >> 16;

IECString sLearnSpellMod;
int nLearnSpellMod;
int nRow = creTarget.cdsPrevious.intelligence;
int nCol = 0; //LEARN_SPELL
if (nCol < g_pChitin->pGame->INTMOD.nCols &&
	nRow < g_pChitin->pGame->INTMOD.nRows &&
	nCol >= 0 &&
	nRow >= 0) {
	sLearnSpellMod = *((g_pChitin->pGame->INTMOD.pDataArray) + (g_pChitin->pGame->INTMOD.nCols * nRow + nCol));
} else {
	sLearnSpellMod = g_pChitin->pGame->INTMOD.defaultVal;
}
sscanf_s((LPCTSTR)sLearnSpellMod, "%d", &nLearnSpellMod);

int nRand = IERand() * 100 >> 15;

if (g_pChitin->cNetwork.bSessionOpen &&
	g_pChitin->pGame->m_GameOptions.m_nMPDifficultyMultiplier < 0) {
	nRand = 1;
} else
if (g_pChitin->pGame->m_GameOptions.m_nDifficultyMultiplier < 0) {
	nRand = 1;
}

ResSplContainer resSpell;
resSpell.LoadResource(effect.rResource, TRUE, TRUE);
if (!resSpell.bLoaded) {
	bPurge = TRUE;
	return TRUE;
}

short wLevel = resSpell.GetSpellLevel();
short wType = resSpell.GetSpellType();
int nExclusionFlags = resSpell.GetExclusionFlags();

if (wParam1High & EFFECTLEARNSPELL_RESTRICT_SCHOOL &&
	wType == SPELLTYPE_MAGE &&
	creTarget.GetKitUnusableFlag() & 0x3FC0 & nExclusionFlags) { //0x3FC0 all spell school bits combined
	resSpell.Unload();
	bPurge = TRUE;
	return TRUE;
}

if (wParam1High & EFFECTLEARNSPELL_NO_SORCERER &&
	creTarget.GetCurrentObject().GetClass() == CLASS_SORCERER) {
	resSpell.Unload();
	bPurge = TRUE;
	return TRUE;
}

BOOL bAlreadyKnown = FALSE;
POSITION pos;
if (wParam1High & EFFECTLEARNSPELL_NO_XP_DUPLICATE) {
	switch (wType) {
	case SPELLTYPE_MAGE:
		pos = creTarget.m_KnownSpellsWizard[wLevel - 1].GetHeadPosition();
		while (pos != NULL && bAlreadyKnown == FALSE) {
			CreFileKnownSpell* pKSpell = (CreFileKnownSpell*)creTarget.m_KnownSpellsWizard[wLevel - 1].GetNext(pos);
			if (pKSpell->name == resSpell.name) {
				bAlreadyKnown = TRUE;
			}
		}
		break;
	case SPELLTYPE_PRIEST:
		pos = creTarget.m_KnownSpellsPriest[wLevel - 1].GetHeadPosition();
		while (pos != NULL && bAlreadyKnown == FALSE) {
			CreFileKnownSpell* pKSpell = (CreFileKnownSpell*)creTarget.m_KnownSpellsPriest[wLevel - 1].GetNext(pos);
			if (pKSpell->name == resSpell.name) {
				bAlreadyKnown = TRUE;
			}
		}
		break;
	default:
		break;
	}
}

if (creTarget.m_BaseStats.kit[1] != KIT_TRUECLASS) {
	if (g_pChitin->pGame->GetMageSchool(creTarget.m_BaseStats.kit[1]) == resSpell.GetSpellSchoolPrimary()) {
		nLearnSpellMod += 15;
	} else {
		nLearnSpellMod -= 15;
	}
}

if ((nRand <= nLearnSpellMod) ||
	wParam1High & EFFECTLEARNSPELL_SUCCESS_ALWAYS) { //success
	switch (wType) {
	case SPELLTYPE_MAGE:
		creTarget.AddKnownSpellMage(effect.rResource, wLevel - 1);
		break;
	case SPELLTYPE_PRIEST:
		creTarget.AddKnownSpellPriest(effect.rResource, wLevel - 1);
		break;
	default:
		creTarget.AddKnownSpell(effect.rResource, FALSE);
		break;
	}

	if (!bAlreadyKnown &&
		!(wParam1High & EFFECTLEARNSPELL_NO_XP_ALWAYS) &&
		creTarget.GetCurrentObject().EnemyAlly <= EA_CONTROLLEDCUTOFF) {
		IECString sXPBonus;
		int nXPBonus;
		nRow = 2; //LEARN_SPELL
		nCol = wLevel - 1;

		if (nCol < g_pChitin->pGame->XPBONUS.nCols &&
			nRow < g_pChitin->pGame->XPBONUS.nRows &&
			nCol >= 0 &&
			nRow >= 0) {
			sXPBonus = *((g_pChitin->pGame->XPBONUS.pDataArray) + (g_pChitin->pGame->XPBONUS.nCols * nRow + nCol));
		} else {
			sXPBonus = g_pChitin->pGame->XPBONUS.defaultVal;
		}
		sscanf_s((LPCTSTR)sXPBonus, "%d", &nXPBonus);

		g_pChitin->pGame->AddExperienceParty(nXPBonus);
	}
}

resSpell.Unload();
bPurge = TRUE;
return TRUE;
}

 

The school restrictions are taken from the SPL exclusion flags. Sorcerer simply checks class.

Link to comment

Ok, i see the benefit of those flags. I even see why you added the no_xp_duplicate flag, it is actually to fix a bug in the original, it should never give xp for learning a spell you already know. Is there any instance where it shouldn't be set?

Link to comment
Ok, i see the benefit of those flags. I even see why you added the no_xp_duplicate flag, it is actually to fix a bug in the original, it should never give xp for learning a spell you already know. Is there any instance where it shouldn't be set?

Probably not. I have preserved vanilla behaviour with the setting being off by default (I don't think it is specifically used by vanilla BG2:ToB since Learn Spell primarily exists for scrolls, which must be learnt from the inventory screen [and thus subject to checks in the UI]).

Link to comment

On another note, GemRB appears to introduce stats 203-255 inclusive.

I've just had TobEx release with opcodes 202-208, and 300, hoping to include completely new hard-coded stats from 202-300 and having user-defined stats from 301+.

Is this going to clash?

 

Also, what are the new occupied action and trigger opcodes used by GemRB, if any?

Link to comment
On another note, GemRB appears to introduce stats 203-255 inclusive.

I've just had TobEx release with opcodes 202-208, and 300, hoping to include completely new hard-coded stats from 202-300 and having user-defined stats from 301+.

Is this going to clash?

 

Also, what are the new occupied action and trigger opcodes used by GemRB, if any?

I didn't want to add more than 256 stats so stat codes fit on a byte ???

If i absolutely have to add new user stats, i will add them over 256.

It would be good if you put as many stats as possible to the same place as gemrb.

For further constraints, I had to consider other games too, so i need a lot of stats you probably won't have much use.

Lets see if your new stats and gemrb stats have any in common, and place them first.

Then we can discuss, if some gemrb stats don't worth a stat status :D

Those could be moved out to special fields (like the unselectable variable, i think).

 

We have some semi hardcoded actions, but they are not as fixed as stats.

These are listed in gemact.ids

 

399 ApplySpellPoint(P:Target*,I:Spell*Spell)

399 ApplySpellPointRES(P:Target*,S:SpellName*)

398 ChangeDestination(O:Object*,S:Destination*)

397 UnmakeGlobal(O:Object*)

396 SetToken2DA(S:ResRef*)

395 StartDialogOverride(S:DialogFile*,O:Target*,I:Unused,I:Unused,I:ConverseAsItem)

394 SetTrackString(I:StrRef, I:Flags, I:Difficulty)

148 BashDoor(O:Object)

93 LeaveAreaName(O:Target*)

 

- as you see, we 'fixed' bashdoor, and enabled LeaveAreaName (that you can do with simple action.ids change).

ApplySpellPoint - trivial

ChangeDestination - modifies a travel trigger - used in some iwd unhardcoded stuff, also useful for other BIS games.

UnmakeGlobal - well, this is something you want to consider in support of large npc mods. You can make an npc temporarily global, with all the benefits, then you can dispose it, so it won't clutter the .gam anymore.

SetToken2DA - Sets multiple tokens randomly (one row per token, columns for variable values). String variables (tokens) are very unsupported in the original engine

StartDialogOverride - heh, i have no idea, why we needed this

SetTrackString - changes the track string for an area, so it can be scripted. The flag is a simple boolean for the O_ string. (See tracking.2da in tob). We also added a skill check.

Link to comment

We have some new triggers/objects too, but they are not tied to a fixed place.

HasItemTypeSlot - true if an item type is in an inventory slot. Since we can have custom itemtypes, you can make items that are wielded, but not considered a weapon, or worn on head, but not a shield, etc.

 

PartyHasItem - accepts a numeric flag to check if the item is stolen, equipped, identified, etc.

These flags are the usual inventory flags, plus gemrb extensions (note that we copy the item flags into this dword too):

 

../Inventory.h: IE_INV_ITEM_IDENTIFIED = 1,

../Inventory.h: IE_INV_ITEM_UNSTEALABLE = 2,

../Inventory.h: IE_INV_ITEM_STOLEN = 4,

../Inventory.h: IE_INV_ITEM_UNDROPPABLE =8,

../Inventory.h: IE_INV_ITEM_ACQUIRED = 0x10, //this is a gemrb extension

../Inventory.h: IE_INV_ITEM_DESTRUCTIBLE = 0x20,//this is a gemrb extension

../Inventory.h: IE_INV_ITEM_EQUIPPED = 0x40, //this is a gemrb extension

../Inventory.h: IE_INV_ITEM_SELECTED = 0x40, //this is a gemrb extension

../Inventory.h: IE_INV_ITEM_STACKED = 0x80, //this is a gemrb extension

../Inventory.h: IE_INV_ITEM_CRITICAL = 0x100, //coming from original item

../Inventory.h: IE_INV_ITEM_TWOHANDED = 0x200,

../Inventory.h: IE_INV_ITEM_MOVABLE = 0x400, //same as undroppable

../Inventory.h: IE_INV_ITEM_RESELLABLE = 0x800, //item will appear in shop when sold

../Inventory.h: IE_INV_ITEM_CURSED = 0x1000, //item is cursed

../Inventory.h: IE_INV_ITEM_UNKNOWN2000 = 0x2000, //totally unknown

../Inventory.h: IE_INV_ITEM_MAGICAL = 0x4000, //magical

../Inventory.h: IE_INV_ITEM_BOW = 0x8000, //

../Inventory.h: IE_INV_ITEM_SILVER = 0x10000,

../Inventory.h: IE_INV_ITEM_COLDIRON = 0x20000,

../Inventory.h: IE_INV_ITEM_STOLEN2 = 0x40000, //same as 4

../Inventory.h: IE_INV_ITEM_CONVERSIBLE = 0x80000,

../Inventory.h: IE_INV_ITEM_PULSATING = 0x100000

 

So, you can easily check if a party has a cursed, stolen, or whatever item ???

Link to comment

Hmm, let's see what I've got so far...

//Expanded Stats
APPEND "stats.ids" "202 FIGHTERLEVEL" UNLESS "202 FIGHTERLEVEL"
APPEND "stats.ids" "203 MAGELEVEL" UNLESS "203 MAGELEVEL"
APPEND "stats.ids" "204 CLERICLEVEL" UNLESS "204 CLERICLEVEL"
APPEND "stats.ids" "205 THIEFLEVEL" UNLESS "205 THIEFLEVEL"
APPEND "stats.ids" "206 DRUIDLEVEL" UNLESS "206 DRUIDLEVEL"
APPEND "stats.ids" "207 RANGERLEVEL" UNLESS "207 RANGERLEVEL"
APPEND "stats.ids" "208 EFFECTIVECLERICLEVEL" UNLESS "208 EFFECTIVECLERICLEVEL"
APPEND "stats.ids" "300 WEIGHTALLOWANCEMOD" UNLESS "300 WEIGHTALLOWANCEMOD"

I probably should have checked these with you first, but if needed I believe I can correct them in an update using REPLACE_TEXTUALLY so that, so long as modders use the name rather than the index, BAF scripts will compile correctly using WeiDU. Otherwise, I'll hope modders haven't used 202-208 yet.

 

202-207: pick the level specific to the class named, so accounts for dual-class and multi-class characters.

208: equivalent to the CDerivedStats::GetEffectiveClericLevel() @ 0x4758AD, so gets specific cleric level, in a way such that cleric 1 = ranger 8 = paladin 9

300: totally new stat allows modification of the maximum weight allowance (+/-) beyond that set by STR and STREX

 

The other thing is, with TobEx beta 0018, stats 301+ are user-set with effect opcode 318 (0x13E). So people can do whatever with them. Is this going to clash?

 

 

Actions and triggers

I'll try to pop the GemRB changes in.

If I wanted to add completely new actions and triggers, from which opcode should I start?

Link to comment

240		CLASSLEVELBARBARIAN
241		CLASSLEVELBARD
242		CLASSLEVELCLERIC
243		CLASSLEVELDRUID
244		CLASSLEVELMONK
245		CLASSLEVELPALADIN
246		CLASSLEVELRANGER
247		CLASSLEVELSORCEROR

These shared stats.ids indices seem similar to some of the stats I've been implementing in TobEx. Am I right in that they contain the level of the specific class mentioned? So a Fighter 6/Cleric 5 would have a CLASSLEVELCLERIC of 5?

 

For all else, I'll work from 300+.

 

If I leave some room for new GemRB actions, I can add actions from 500+.

For triggers, I can use 0x*200+.

 

These will give you at least a 99-opcode buffer from which to work with before clashing.

Link to comment
240		CLASSLEVELBARBARIAN
241		CLASSLEVELBARD
242		CLASSLEVELCLERIC
243		CLASSLEVELDRUID
244		CLASSLEVELMONK
245		CLASSLEVELPALADIN
246		CLASSLEVELRANGER
247		CLASSLEVELSORCEROR

These shared stats.ids indices seem similar to some of the stats I've been implementing in TobEx. Am I right in that they contain the level of the specific class mentioned? So a Fighter 6/Cleric 5 would have a CLASSLEVELCLERIC of 5?

 

For all else, I'll work from 300+.

 

If I leave some room for new GemRB actions, I can add actions from 500+.

For triggers, I can use 0x*200+.

 

These will give you at least a 99-opcode buffer from which to work with before clashing.

 

You are right about the classlevel stats (they came from iwd2). I still don't know how will i make this binary compatible with IWD2, because our stat slots are hardcoded.

 

About scripting:

It is enough if you add actions over 400 (we count down from 399). Though this also means we cannot implement that action with the same number (those are simple arrays that are are limited to 400).

We just started at 400 because those actions are intended to be all the same in our simulated (legacy) engine versions.

 

If we are meant full (binary) script compatibility between tobex and gemrb, you better start somewhere lower than 400, and we sync the numbers.

If full compatibility is not a goal, it is fine for you to start at 400.

For triggers, we don't have any, but if i convert our repeat effect on condition opcode, we might hardcode some.

But those must be also negotiated for full spell/script compatibility.

Our trigger table ends at 255, but i'm willing to increase this.

Our object table ends at 128.

 

Why don't you just start from the first available slot in ToB (you are hacking the legacy code afterall, so you can do that without any fear of overwriting something). To achieve full compatibility, GemRB must use the same opcodes for the same actions, it is just GemRB is already flexible in assigning the opcodes to functions.

Link to comment

I don't think TobEx will ever be compatible with GemRB. However, when people wish to play mods on GemRB that rely on TobEx, I imagine players/modders will start to request that the same features introduced in TobEx be coded into GemRB. This is the important point where I'd like to ensure that TobEx features do not clash, making it easier for modders to simply 'slot' their mods onto the GemRB platform.

 

Even if opcodes are easily transformable, it would be simpler for GemRB to try and keep static opcodes, so I plan not to override any existing opcode, especially if modders wish to use a similar new trigger/action on a different platform. Hence me trying to keep the opcodes unique and non-overlapping.

 

So, in summary, I'll start from

stats 301+

actions 401+

triggers 0x*100+ (IWD2 ends at 0xF8, which would only leave you 7 slots before buffer overrun, is that enough if you plan for extra opcodes?)

Link to comment
I don't think TobEx will ever be compatible with GemRB. However, when people wish to play mods on GemRB that rely on TobEx, I imagine players/modders will start to request that the same features introduced in TobEx be coded into GemRB. This is the important point where I'd like to ensure that TobEx features do not clash, making it easier for modders to simply 'slot' their mods onto the GemRB platform.

 

Even if opcodes are easily transformable, it would be simpler for GemRB to try and keep static opcodes, so I plan not to override any existing opcode, especially if modders wish to use a similar new trigger/action on a different platform. Hence me trying to keep the opcodes unique and non-overlapping.

 

So, in summary, I'll start from

stats 301+

actions 401+

triggers 0x*100+ (IWD2 ends at 0xF8, which would only leave you 7 slots before buffer overrun, is that enough if you plan for extra opcodes?)

 

Stats: we are already stuffed at 256, so that's fine. I will have to introduce an extra translation layer (later).

For triggers, i don't mind what you do, but i think you could just continue upwards from wherever ToB ended. It's not ToBEx with what GemRB would clash here, so you can ignore this.

Actions: i would definitely suggest you continue from wherever ToB ended. I'm pretty sure we got enough room.

Even if we don't, i will just change gemrb's fixed slots. No one will notice :) I would however not want to increase the action lookup table.

Link to comment

Query about GemRB implementation of effect #166.

 

There is a disparity in implementation between games with BG2:ToB and IWD2 using a two param2 option, and the other games using a three param2 option. For BG2:ToB, the param2=0 cumulative modifier sets the current derived stats making it an instantaneous incrementation (and thereefore overridden by any stacking param2=1 modifiers). This is not identical to something like Strength Mod where the cumulative modifiers sets the increment derived stats so that it is not overridden by param2=1 modifiers.

 

Do you know if this is the case for IWD2? How do the other games use param2=0.

 

It looks like GemRB has a more simple implementation. Can you described how GemRB implements this opcode?

Link to comment

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...