If you've read the Obsidian documentation of the SOZ OM before, you'll remember that it's less than fully clear. I am (slowly) documenting the SoZ Overland Map system, much like I did for the SoZ trade system (link). If you have specific questions about how something on the OM works, ask them here and I will make sure it gets covered in the documentation.
Questions you want answered about the SoZ OM
#1
Posté 06 avril 2014 - 01:40
- rjshae et Rolo Kipp aiment ceci
#2
Posté 06 avril 2014 - 03:26
Very cool Kamal, and much needed.
It's great that we keep documenting this stuff. Happy to see it!
#3
Posté 06 avril 2014 - 06:58
Here is an example of what you'll get, from the section covering notable functions.
AwardEncounterXP(), handles granting of XP to players when they leave the encounter map. Refers to the nx2_enc_spawnrewards.2da in determining how much XP to give. One trick in SoZ is to summon creatures before leaving in order to increase party size in order to gain more XP from the encounter. This reason this works is not due to party size, but because the summoned creatures are generally lower level than the party, and thus decrease the average party level, which is what is used in determining the XP award. Thus if you want to remove this trick, you need to be looking at the two while loops in the function, and remove the || GetIsOwnedByPlayer(oTarg) part:
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
nPartyMembers++;
oTarg = GetNextFactionMember(oPC, FALSE);
}
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
GiveXPToCreature( oTarg,nXP );
oTarg = GetNextFactionMember(oPC);
}
- Rolo Kipp aime ceci
#4
Posté 06 avril 2014 - 07:02
At this point I have a pretty good grasp on the 2das, terrain triggers, encounters, locations and hidden locations. What I need to look into now is the random encounter areas and the goodie system. Then I will need to build a demo OM to illustrate each part of the OM system aside from trade. The trade system, including trade caravans, I already did in my documentation/demo of the trade system.
- Rolo Kipp aime ceci
#5
Posté 06 avril 2014 - 09:10
... One trick in SoZ is to summon creatures before leaving in order to increase party size in order to gain more XP from the encounter. This reason this works is not due to party size, but because the summoned creatures are generally lower level than the party, and thus decrease the average party level, which is what is used in determining the XP award. Thus if you want to remove this trick, you need to be looking at the two while loops in the function, and remove the || GetIsOwnedByPlayer(oTarg) part:while(GetIsObjectValid(oTarg)) { if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg)) nPartyMembers++; oTarg = GetNextFactionMember(oPC, FALSE); } while(GetIsObjectValid(oTarg)) { if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg)) GiveXPToCreature( oTarg,nXP ); oTarg = GetNextFactionMember(oPC); }
hey Kam_
GetIsOwnedByPlayer() is a truePC (not player-created, not companion, these two *are* RosterMembers). ergo not so sure you'd want to remove that to prevent a summon-critter/get-more-XP exploit. now... i'm gonna fly by and say that Associates (summons, henchmen, etc.) are not included in that count. They're neither roster members, nor Owned.
Associates are,
GetAssociateType(oTarg) != ASSOCIATE_TYPE_NONE;
- kamal_ aime ceci
#6
Posté 06 avril 2014 - 09:11
GetIsOwnedByPlayer(oTarg) part:
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
nPartyMembers++;
oTarg = GetNextFactionMember(oPC, FALSE);
}
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
GiveXPToCreature( oTarg,nXP );
oTarg = GetNextFactionMember(oPC);
}
#7
Posté 06 avril 2014 - 10:02
hey Kam_
GetIsOwnedByPlayer() is a truePC (not player-created, not companion, these two *are* RosterMembers). ergo not so sure you'd want to remove that to prevent a summon-critter/get-more-XP exploit. now... i'm gonna fly by and say that Associates (summons, henchmen, etc.) are not included in that count. They're neither roster members, nor Owned.
Associates are,
GetAssociateType(oTarg) != ASSOCIATE_TYPE_NONE;
Hmm that's the code for counting up the party size. How would you do it to avoid summons/familiars?
#8
Posté 06 avril 2014 - 10:34
while(GetIsObjectValid(oTarg))
{
if ((GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
&& GetAssociateType(oTarg) == ASSOCIATE_TYPE_NONE)
{
nPartyMembers++;
}
oTarg = GetNextFactionMember(oPC, FALSE);
}but that's redundant because, this should also work:while(GetIsObjectValid(oTarg))
{
if (GetAssociateType(oTarg) == ASSOCIATE_TYPE_NONE)
{
nPartyMembers++;
}
oTarg = GetNextFactionMember(oPC, FALSE);
}I need to see the code that divides nXP by nPartyMembers,
ps. These are the associates
int ASSOCIATE_TYPE_NONE = 0; int ASSOCIATE_TYPE_HENCHMAN = 1; int ASSOCIATE_TYPE_ANIMALCOMPANION = 2; int ASSOCIATE_TYPE_FAMILIAR = 3; int ASSOCIATE_TYPE_SUMMONED = 4; int ASSOCIATE_TYPE_DOMINATED = 5;
- kamal_ et Rolo Kipp aiment ceci
#9
Posté 06 avril 2014 - 11:10
Here is the entire function. It's widely thought (I've seen many threads on it) that having summoned help gets the party granted extra xp when they exit the encounter. Maybe that's just not true?
void AwardEncounterXP(int nXP)
{
SendMessageToAllPlayersByStrRef(STRREF_ENC_XP_AWARD);
object oPC = GetFirstPC();
object oTarg = GetFirstFactionMember(oPC, FALSE);
int nPartyMembers = 0;
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
nPartyMembers++;
oTarg = GetNextFactionMember(oPC, FALSE);
}
nXP /= nPartyMembers;
oTarg = GetFirstFactionMember(oPC);
while(GetIsObjectValid(oTarg))
{
if(GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
GiveXPToCreature( oTarg,nXP );
oTarg = GetNextFactionMember(oPC);
}
}
#10
Posté 06 avril 2014 - 11:38
uh, that function has no exploit, Kam_
in fact, if it were counting Associates as party members, it would *lessen* the awardedXP to each character, when summoning more of them:
nXP /= nPartyMembers;
But, since you described the potential exploit very well in one of the above posts, that *if* and most likely *when* doing an XP calculation that involves *average* party level ( there's a function for that, btw : GetFactionAverageLevel(), tho I don't know if it includes associates.. /shrug ) -- we can assume that there *are* places where the exploit is valid.
just not in that function.
and, If you want an indepth analysis of what is and isn't a RosterMember, what is and isn't an OwnedCharacter, what is and isn't an Associate, etc. copy and compile my "c-info" from the vault, then control a character and target a familiar and have a look at the chat-debug ...,
(perhaps I could have made those messages/function calls a bit clearer..)
... here's a possibility that would re-instate an exploit: If passed in nXP was previously multiplied by partymembers including Associates, then by desummoning those Associates the remaining nonAssociates would indeed get more XP. (depends on how, i assume, the .2da lookup is handled -- i imagine it's just "hey, have some XP!")
- kamal_ aime ceci
#11
Posté 06 avril 2014 - 11:46
wait a second, I think there's a 'bug'
GiveXPToCreature() should only be executed on the PC ... iirc other eligible faction members then automatically get that XP also
i can check that if you want,
- kamal_ aime ceci
#12
Posté 06 avril 2014 - 11:59
Tracing the int nXP up through the functions in ginc_overland (via GetEncounterXP() containing nPartyCR to int nPartyCR = GetPartyChallengeRating() in DisplayChallengeRating() leads to the function GetPartyChallengeRating(), beginning on line 463):
int GetPartyChallengeRating()
{
object oPartyMember = GetFirstFactionMember(GetFirstPC(), FALSE);
int nHD;
int nTotalPartyLevels;
int nPartySize;
float fPartyCR;
while (GetIsObjectValid(oPartyMember))
{
nHD = GetTotalLevels(oPartyMember, FALSE);
nTotalPartyLevels = nTotalPartyLevels + nHD;
nPartySize++;
oPartyMember = GetNextFactionMember(GetFirstPC(), FALSE);
}
fPartyCR = IntToFloat(nTotalPartyLevels) / IntToFloat(nPartySize);
return FloatToInt(fPartyCR);
}
#13
Posté 07 avril 2014 - 12:03
ah, lemme think
#14
Posté 07 avril 2014 - 12:48
ok so it appears that the .2da lookup is based off 'totalPartyCR' ...
which includes Associates ...
So, if the partyCR gets calculated without any Associates it will generally be higher (because lowlevel Assocs would have brought it down). Then I'd think the awardedXP is lower, because the high partyCR makes the encounter less difficult. ( a browse through 'nx2_enc_xpawards.2da' confirms this : higher partylevelCR, less experience for the same EncounterLevel )
and beyond that, summoning or desummoning Assocs makes no difference, once nXP is passed into AwardEncounterXP()
The exploit, it seems to me, is to always have Associates in the party (when the .2da lookup is done) -- which lowers partyCR and increases awardedXP, but ironically the fight is easier because, hey you've got associates.
by this reasoning, as long as Associates are summoned before the nx2_enc_xpawards.2da lookup, it's an exploit yep
I guess the way to pseudo-fix it is to change GetPartyChallengeRating(), to exclude associates. But that leads to a conundrum, because then we're not getting an accurate partyCR. A better way is to change AwardEncounterXP() to *include* associates -- but then throw their experience away.
void AwardEncounterXP(int nXP)
{
SendMessageToAllPlayersByStrRef(STRREF_ENC_XP_AWARD);
int nPartyMembers = 0;
object oPC = GetFirstPC();
object oTarg = GetFirstFactionMember(oPC, FALSE);
while(GetIsObjectValid(oTarg))
{
// include all faction, incl. Assocs:
nPartyMembers ++;
oTarg = GetNextFactionMember(oPC, FALSE);
}
nXP /= nPartyMembers;
oTarg = GetFirstFactionMember(oPC);
while (GetIsObjectValid(oTarg))
{
if (GetIsRosterMember(oTarg) || GetIsOwnedByPlayer(oTarg))
{
GiveXPToCreature(oTarg, nXP);
}
oTarg = GetNextFactionMember(oPC);
}
}see what i did thar? ( again, i'm bypassing the possible anomaly w/ GiveXPToCreature() for now )
- kamal_ aime ceci
#15
Posté 07 avril 2014 - 01:05
That way the PC & Companions get a relative bonus for not having associates up. Because they'll suck up a disproportionate amount of the awardedXP ... relative to their contribution to the battle. (granted, not ideal either, but i like little penalties like that when playing -- and to code it properly would require, uh, several extra lines.)
- kamal_ aime ceci
#16
Posté 07 avril 2014 - 01:21
The exploit, it seems to me, is to always have Associates in the party (when the .2da lookup is done) -- which lowers partyCR and increases awardedXP, but ironically the fight is easier because, hey you've got associates.
by this reasoning, as long as Associates are summoned before the nx2_enc_xpawards.2da lookup, it's an exploit yep
Yep. The XP for the encounter is awarded when the player chooses to return to the OM, via the conversation that pops up when the party reaches the random encounter area exit.
The party can choose to fight without summons, and then as they approach the exit summon their critters. The function grants them extra XP for having the summons (since the summons bring down average Party level), who did not participate in the battle at all. Or the party can use their summons in battle as you said, both making the battle easier and giving the party more XP for it. I don't think standard NWN2 combat gives increased XP when the party uses summons, and as far as I am aware using summons has never meant more XP in PnP, just a potentially easier battle granting the same XP as it otherwise would.
Theoretically, if the summon spells were changed to allow multiple summons, you could summon up a giant army of CR1 creatures and bring the AveragePartyCR way down. This would mean the party would get a large xp boost. The 2da shows you get something like 20 thousand xp for a CR 1 party handling a CR 30 encounter...
I'll have to check out the code you posted tomorrow, but it seems like it would be straightforward to test since you could compile ginc_overland with it and stick it in the override, load up a SoZ save and run an encounter, then exit and remove the override and load up the save again.
#17
Posté 07 avril 2014 - 02:34
Kam_
the only place I find AwardEncounterXP() called from is 'ga_leave_encounter.nss'
recompile that after/between changes to AwardEncounterXP() and it should be gtg. ( note: there is a 'ga_leave_encounter.nss' in Data\scripts_X2.zip and another in \Neverwinter Nights 2 Campaign_X2
- they are different and the one in Campaign_X2 has priority.
[continued]
i did some testing on GiveXPToCreature()
used the following code, run from the console:
void main()
{
GiveXPToCreature(OBJECT_SELF, 100);
}
Party consisted of
1. PC
2. Companion (Sand)
3. Sand's familiar
results:
- familiar never gets xp
- when PC is controlled, both PC and Companion get Xp
- when Companion is controlled, only Companion gets XP
- when familiar is controlled, nobody gets xp.
... which is pretty darn odd when you think about leaving the randomEncounter area. Since, as fleshed out above, the totalXP will be divided among partymembers but then each companion gets a double share, by receiving their own allotment plus the PC's
Pls keep your eyes open for this further 'anomaly' if/when testing ....
here's a replacement for GiveXP, if it goes odd
SetXP(oTarg, GetXP(oTarg) + nXP);
Modifié par kevL, 07 avril 2014 - 08:04 .
#18
Posté 13 avril 2014 - 11:41
Part of the section on random OM encounters, this bit covers the functions used in encounter conversations. It also will change some as I add detail to parts, like the function that covers the base for bribe amounts:
When the party is drawn into a fight with a hostile encounter, they are transported an encounter area determined by the terrain. This happens based on functions called from the encounter conversation.
Encounter conversation:
The encounter conversations are set by the sConv column of the om_enc_x_"AreaName".2da. As a result there is no default name. However all conversations have the following in common.
ga_initiate_encounter(): Called from the conversation node that will start the encounter ("Fight the monsters!"), this is the function that transfers the party to the encounter area. The most important part here is the call to InitiateEncounter(), which is defined in ginc_overland.
ka_encounter_end(): Called when the party ends the encounter in a manner that means the encounter will not continue to pursue the party, without going to the encounter map, for instance if the party has the same deity as the encounter or the party has paid a bribe to make the encounter go away. This script causes the encounter to move away from the party and despawn.
ka_encounter_evade(): if the party chooses to evade a hostile encounter, the encounter is stunned for a variable number of seconds. When the stun ends, the encounter will continue to pursue the party. This and ka_encounter_end() are the two ways to end an encounter without a trip to the encounter map.
ga_initiate_encounter(): Begins the encounter. The first two variables allow the party to use skills to affect the encounter, normally by using bluff or intimidate. There are other variables, but I have not seen any case where they were not zero is SoZ. Successfully checks cause the "Off Guard" (Bluff) or "Shaken" (Intimidate) status on the encounter creatures. "Shaken" decreases the monster's AB by 2, "Off Guard" decreases monster AC by 2. Both also decrease monster saves by 2.
ka_encounter_*tanglefoot*(): Called when the party uses a tanglefoot bag to aid their escape from the encounter. ka_encounter_tanglefoot is a regular tanglefoot. ka_encounter_imptanglefoot is an improved tanglefoot, ka_encounter_grttanglefoot is a greater tanglefoot, ka_encounter_perftanglefoot is a perfect tanglefoot. The conversation should include the conditional checks for the relevant tanglefoot bags. Remember to also use ga_destroy_item to destroy the tanglefoot bag used, for whatever reason destroying the bag was done separatately from the ka_encounter_*tanglefoot*() script in SoZ.
ga_bribe_amount(): Runs when the party attempts to bribe an encounter.
ga_mod_bribe(): If the party attempts to bribe, this function can be used to modify the bribe. This is normally applied based on a successful skill check. The skill it checks is Diplomacy by default, and this default is in the script. The mod can cut the bribe price by up to 50%. Sigh, why didn't they check the skill used to try to get a mod to the bribe... I'll need to fix that at some point.
gc_can_pay_bribe(): conditional used to determine if the party has enough gold to pay the bribe. Because the amount of bribe requested is variable, this conditional checks the variable amount instead of using a conditional check for x gold.
#19
Posté 13 avril 2014 - 11:42
I've got a basic handle on goodies, but need to delve into them more and redo things to remove the references to the tlk file to simplify modding things, like I had to do for the trade system.
#20
Posté 17 avril 2014 - 12:53
I've implemented the goodie system (currently minus granting the goods when you use the goodie). The system is basically random encounters for placeables, so it could be used for something like a quest of "collect 10 crystals in the crystal forest", and the goodie system would place the crystals randomly throughout the map, in valid walkable locations, enhancing replayability. Or you could make combat maps with dynamically placed obstacles.

#21
Posté 17 avril 2014 - 09:34
Kamal,
What determines the loot given in the goodie system? that was the last piece I was never able to get working right and so just made a work around system with neutral encounters and VFXs.
#22
Posté 17 avril 2014 - 11:20
How I do the documentation is I step through the various systems in the known working system (ie SoZ), seeing what scripts call what functions etc, documenting as I go. Once I feel I've documented something, I then start copying the resources I've identified as being used over from the known good system and setting up my own test module. As I find issues with my test module I look and see where I missed something and then copy those resources over.
However, there was a problem with the goodie system. It doesn't appear to actually work as implemented by SoZ. Yes, I know it does work of course, but it shouldn't. The function that creates the goodies on the map doesn't actually create goodies on the map, it's creates ipoints on the map and there is no part after that that creates a goodie at the ipoint. The goodies are never created, only the ipoints. The function that creates the goodies also never calls the function that sets the local variables that determine the treasure on the goodies, and the goodie blueprints don't have these variables on them (this is why my goodies didn't give stuff yesterday).
#23
Posté 17 avril 2014 - 11:25
Kamal,
What determines the loot given in the goodie system? that was the last piece I was never able to get working right and so just made a work around system with neutral encounters and VFXs.
The loot is determined by the goodie. Each goodie exists in a 2da which defines the skill needed to find, the DC of the skill, and what the rewards of using the goodie are. Goodies will only get spawned in terrain triggers, the goodie creation function checks for existence of these triggers. Most of the goodie placeables that get spawned are several different goodies under the hood, but using the same placeable, so there is some randomness to the reward of the same placeable on the map.
- rjshae aime ceci
#24
Posté 18 avril 2014 - 04:28
The function generates an ipoint from a blueprint that has a heartbeat script attached to it, and sets several local variables on the ipoint, which its heartbeat script uses to determine what loot to spawn. The ipoint's heartbeat script (gb_hidden_loc_hb) spawns the special "appearing" effect, some text feedback, and the goodie placeable itself. I use this technique myself in some of my scripting systems.
- rjshae, kamal_ et Rolo Kipp aiment ceci
#25
Posté 18 avril 2014 - 01:58
The function generates an ipoint from a blueprint that has a heartbeat script attached to it, and sets several local variables on the ipoint, which its heartbeat script uses to determine what loot to spawn. The ipoint's heartbeat script (gb_hidden_loc_hb) spawns the special "appearing" effect, some text feedback, and the goodie placeable itself. I use this technique myself in some of my scripting systems.
You are indeed correct. I had brought in the ipoints but not the hb script for them.





Retour en haut







