How to decompose a compound tag into it's constituent simple tags?

+
How to decompose a compound tag into it's constituent simple tags?

In D'Jinni, things (and particularly character templates) can have compound tags - tags with several elements separated by semicolons. I need a method of decomposing the compound tags into simple tags: For example, where I have the compound tag
Code:
bv_npct_ksenia; bv_fact_pagf; PagF2PagF; PagF2ConvF
I want to be able to extract just [tt]bv_npct_ksenia[/tt]. In Java one would use a StringTokenizer; in C one would iterate over (a copy of) the string replacing semi-colons with nulls; in PERL one would use split(). If I could simply find the position of the first semi-colon in the string then I could use GetStringLeft() to recursively split off the first sub-token until I'd got them all. But as far as I can see the NWNScript in D'Jinni doesn't have any means of doing this.Suggestions, please!modified to add....Stand down, troops! Solved it. The solution is:
Code:
string FirstSubTag( string compoundTag) {	int separatorPos = FindSubString( compoundTag, ";");	string result = compoundTag;		if ( separator > -1) {		result = GetStringLeft( compoundTag, separatorPos);	}		// PrintString( "FirstSubTag in = '" + compoundTag + "'; out = '" + result + "'");		return result;}
 
Hi Simon, excuse me, I haven't any skill at programming, I'm just trying for two days to understand what kind of situation belong to your script, is it useful for conditionnal conversation if the player solve just some parts of a big quest? :-[ sorry to ask that, but if I don't ask I will never know....
 
HexenmeisterRaven said:
That would really interest me, too. :)So for what exactly shall this script be in the end?
Sorry, guys! I edited my first post to post the answer, but I'll put it here again, with a fuller explanation of how we're resolving between tags and NPC 'Res Refs' (Resource references, actually filenames).The actual problem I've asked about is solved by FirstSubTag, here:
Code:
// Identify the first part in this tag, if it is a compound tag; otherwise return// the whole tag.string FirstSubTag( string compoundTag) {	int separator = FindSubString( compoundTag, ";");	string result = compoundTag;		if ( separator > -1) {		result = GetStringLeft( compoundTag, separator);	}		// PrintString( "FirstSubTag in = '" + compoundTag + "'; out = '" + result + "'");		return result;}
I could have done a recursive function to find any sub-tag, but that wasn't what I needed; our convention is that the character's own tag is their first sub-tag. To explain how all this works I'll include the whole of our characters include file and discuss what things do:
Code:
//////////////////////////////////////////////////////////////////////////	inc_bv_chars.nss// 	Include file for things which manipulate characters //	in Birth and Virgins.////	(c) 2008 Petra Prinz, Carrol Dufault, Simon Brooke, Cory Kerens//	The Medusa Collective////////////////////////////////////////////////////////////////////////// ------------------------------------------------------------------------------------------------// -------- Magic constant property name on which to look for characters'// -------- NPC resrefs// ------------------------------------------------------------------------------------------------const string NPC_RESREF="NPC_RESREF";
The reason for declaring a constant which has the same name as its string value is that the compiler can check constants and will therefore catch spelling mistakes and typos. Seeing I make a lot of typos that's useful to me!
Code:
// ------------------------------------------------------------------------------------------------// -------- Magic constants for reading the dramatis personae file// ------------------------------------------------------------------------------------------------const string DRAMATISPERSONAE="bv_characters";const string DPCOLUMNTAG="tag";const string DPCOLUMNTEMPLATERESREF="template";const string DPCOLUMNSTORYNPCRESREF="npc";const string DPCOLUMNNAME="name";const string DPCOLUMNALTTEMPLRESREF="alttemplate";
This stanza describes the structure of our 'bv_characters' data table, which actually holds the resolution information.
Code:
// ------------------------------------------------------------------------------------------------// -------- Character tags: principal characters// ------------------------------------------------------------------------------------------------const string abigTag = "bv_npct_abigail";const string abiiTag = "bv_npct_abiinn";const string agnsTag = "bv_npct_agn_s";const string agnTag = "bv_npct_agnieszka";const string barnTag = "bv_npct_barnabas";const string dandTag = "bv_npct_dandelion";const string granTag = "bv_npct_grandpa";const string gutsTag = "bv_npct_gut_s";const string gutkTag = "bv_npct_gutka";const string helaTag = "bv_npct_hela";const string josTag = "bv_npct_josef";const string martTag = "bv_npct_martyna";const string revsTag = "bv_npct_rev_s";const string revTag = "bv_npct_reverend";const string rozTag = "bv_npct_rozalia";const string szsTag = "bv_npct_sz_s";const string szczTag = "bv_npct_szczepan";// ------------------------------------------------------------------------------------------------// -------- Character tags: extras// ------------------------------------------------------------------------------------------------/* random villagers - women */const string annaTag = "bv_npct_anna";const string barbTag = "bv_npct_barbora";const string hannTag = "bv_npct_hanna";const string jadwTag = "bv_npct_jadwiga";const string jolaTag = "bv_npct_jolanta";const string kataTag = "bv_npct_katarzyna";const string krysTag = "bv_npct_krysia";const string ksenTag = "bv_npct_ksenia";const string tabiTag = "bv_npct_tabitha";/* random villagers - men */const string jakuTag = "bv_npct_jakub";const string jerzTag = "bv_npct_jerzy";const string karoTag = "bv_npct_karol";const string miesTag = "bv_npct_mieszko";const string paweTag = "bv_npct_pawel";const string radoTag = "bv_npct_radomil";const string tobiTag = "bv_npct_tobias";	/* random villagers - girls */const string alinTag = "bv_npct_alina";const string celiTag = "bv_npct_celina";const string elzbTag = "bv_npct_elzbieta";const string rutaTag = "bv_npct_ruta";/* random villagers - boys */const string aronTag = "bv_npct_aron";const string eriaTag = "bv_npct_eriash";const string krstTag = "bv_npct_krysztof";const string rafaTag = "bv_npct_rafal";/*travelers*/const string andrTag = "bv_npct_andrzej";const string hawtTag = "bv_npct_haw";const string jaceTag = "bv_npct_jacek";const string tomzTag = "bv_npct_tomasz";
Once again, declaring constants means the compiler will catch any spelling errors or typos. All our characters, even the extras, are declared with full 'Story NPC' files; this is so that we can have different behaviours for each character in different phases of our story.
Code:
// ------------------------------------------------------------------------------------------------// -------- Utility functions// ------------------------------------------------------------------------------------------------// Identify the first part in this tag, if it is a compound tag; otherwise return// the whole tag.string FirstSubTag( string compoundTag) {	int separator = FindSubString( compoundTag, ";");	string result = compoundTag;		if ( separator > -1) {		result = GetStringLeft( compoundTag, separator);	}		// PrintString( "FirstSubTag in = '" + compoundTag + "'; out = '" + result + "'");		return result;}
This is the one I've already discussed above...
Code:
// a wrapper around GetNearestObjectByTag which allows for// the fact that the object we're interested in could be OBJECT_SELF// argument tag: a string, presumed to be the tag of an NPC// returns: that NPC (or null if not found, but we hope that// won't happenobject GetNPCByTag( string tag) {	object result;		if ( GetTag( OBJECT_SELF) == tag) {		result = OBJECT_SELF;	} else {		result = GetNearestObjectByTag( tag);	}	return result;}
This is simply a useful function to have...
Code:
// get the NPC resref associated with this tag; note, this data is initialised// from bv_characters.2da - it ain't magic.string GetNPCResRefFromTag( string tag) {	int row = Get2DARow( DRAMATISPERSONAE, FirstSubTag( tag), 0);	string result = 		Get2DAString( DRAMATISPERSONAE, DPCOLUMNSTORYNPCRESREF, row);	// PrintString( "GetNPCResRefFromTag resolved '" + 	//	tag + "' to '" + result + "'");		return result;}
This is the function which actually does the lookup in the [tt]DRAMATISPERSONAE [/tt]table. [tt]Get2DARow [/tt]returns the record in the table which describes the character we're interested in. Get2DAString pulls the content of the [tt]DPCOLUMNSTORYNPCRESREF[/tt] field out of that record. I've included an extract from the [tt]DRAMATISPERSONAE [/tt]table at the end of this post.
Code:
// get the NPC resref associated with this object; note, this data is initialised// from bv_characters.2da - it ain't magic.string GetNPCResRefFromObject( object o) {	string result = GetNPCResRefFromTag( GetTag( o));		return result;}
This is just a wrapper around the previous function to allow us to pass an object.
Code:
// move this npc to this station immediately. If they're too far away, jump them there// even if it causes a visual glitchvoid MoveToStation( object npc, object station) {	if ( GetDistanceBetween( npc, station) > 8.0) {		/* disaster - they aren't nearby, we'll have to jump them there */		AssignCommand( npc, JumpToLocation( GetLocation( station)));	} else {		/* more or less there, walk them to station - but immediately! */		AssignCommand( npc, ClearAllActions(TRUE));		AssignCommand( npc, ActionForceMoveToLocation( GetLocation( station)));	}}
This is just a utility function the purpose of which should be obvious.Finally, here's the first few lines of DRAMATISPERSONAE:
Code:
2DA V2.0	tag       	template  	npc       	name   	alttemplate  0	bv_npct_abigail 	bvabigail 	bv_npc_abigail 	Abigail 	bvabiinn1	bv_npct_abiinn  	bvabiinn  	bv_npc_abigail 	Abigail 	****     2	bv_npct_agnieszka	bvagnieszka	bv_npc_agnieszka	Agnieszka	bvagn_s    3	bv_npct_agn_s  	bvagn_s  	bv_npc_agnieszka	Agnieszka	****     4	bv_npct_alina  	bvalina  	bv_npc_alina  	Alina  	****     5	bv_npct_anna   	bvanna   	bv_npc_anna   	Anna   	****     6	bv_npct_aron   	bvaron   	bv_npc_aron   	Aron   	****     7	bv_npct_barbora 	bvbarbora 	bv_npc_barbora 	Barbora 	****     8	bv_npct_barnabas 	bvbarnabas 	bv_npc_barnabas 	Barnabas 	****     9	bv_npct_celina  	bvcelina  	bv_npc_celina  	Celina  	****     10	bv_npct_dandelion	bvdandelion	bv_npc_dandelio 	Dandelion	****
The '[tt]tag[/tt]' column contains the character's tag. The [tt]template [/tt]column contains the 'Res Ref' (file name) of character's default character template. The [tt]npc [/tt]column contains the res ref of the character's Story NPC file. The [tt]name [/tt]column contains the characters name; and the [tt]alttemplate [/tt]column contains the res ref of an alternative template for that character if she has one. For example, when Abigail is in her home, she has a store at which she sells alchemic components; when she's in the inn she sells food and beer. As a character template can only have one store, she needs two character templates.
 
Simon, sometimes I wonder why you make some things that complicated....For example: Your Abigail has two templates, so she can have two shops, for such things you have made the "Characters.2da".My mod is running without all this, I have only made the NPC-Story-file, put both templates in it (Template 1 from 8:00 to 11:00 with shop A, and template 2 from 11:00 to 13:00 with shop B), now both shops are called with a dialog, Shop A / dialog A and shop B / dialog B, all working fine, because the dialogs are stored in the NPC-file and it's also stored there, which template the character is using. Both templates have two different tags for this action, but for some other actions, where I don't know which template is running when the player arrives they have the same tag in the second place. (If you know what I mean)But with the JumpCommand you have made me curious:As you know there's a cutscene in my mod, when Geralt has to do his decision at the marketplace. For this decision I set the Jump Command for all characters that shall be in the market at this time (when the script for the cutscene is called from Eskel's dialog). It worked perfectly until the EE came, now with the EE the jump command seems not longer to work, all the people that are in the same area (in this case the village area) are only "walking" to the marketplace (MoveToLocation command), they don't want to do my JumpToLocation command. The Jump command is only working, when the characters are in another area, for example a house.So I'm wondering if your "station" command really works correctly. I mean this part:
Code:
if ( GetDistanceBetween( npc, station) > 8.0) {		/* disaster - they aren't nearby, we'll have to jump them there */		AssignCommand( npc, JumpToLocation( GetLocation( station)));
Not that I really want to do more bugfixing on this damned thing, I have no more nerves for this at the moment... :pIt only would be of interest, if the jump command does work with the distance command before.
 
HexenmeisterRaven said:
Simon, sometimes I wonder why you make some things that complicated....
I'm a geek. These things aren't complicated to geeks :)Basically we have the same set up as you have, but I want to be able to resolve Story NPC res refs from tags, hence the lookup-table. If I was designing a game engine (as I'm slightly tempted to do for our next project) it would be much more object oriented than Aurora and you wouldn't have to do this sort of hackery.
But with the JumpCommand you have made me curious:As you know there's a cutscene in my mod, when Geralt has to do his decision at the marketplace. For this decision I set the Jump Command for all characters that shall be in the market at this time (when the script for the cutscene is called from Eskel's dialog). It worked perfectly until the EE came, now with the EE the jump command seems not longer to work, all the people that are in the same area (in this case the village area) are only "walking" to the marketplace (MoveToLocation command), they don't want to do my JumpToLocation command. The Jump command is only working, when the characters are in another area, for example a house.So I'm wondering if your "station" command really works correctly. I mean this part:
Code:
if ( GetDistanceBetween( npc, station) > 8.0) {		/* disaster - they aren't nearby, we'll have to jump them there */		AssignCommand( npc, JumpToLocation( GetLocation( station)));
Not that I really want to do more bugfixing on this damned thing, I have no more nerves for this at the moment... :pIt only would be of interest, if the jump command does work with the distance command before.
It certainly works as advertised. Whether since EE it's no longer needed I don't know.
 
It certainly works as advertised. Whether since EE it's no longer needed I don't know.
There are several things / commands that have changed with the EE, for example:Before the EE I used this command to set the profile of a character
Code:
SetProfile(oNPC, PROFILE_TYPE_AFFILIATION_WITCHER, TRUE);
It has worked fine, then the EE came and it doesn't work any longer. Well, the old script is still working in the game, but you can't compile the same command line any longer, because of the changings CDPR has made to these commands with the EE.There's one bug in my mod, where I wondered that this line won't compile and I found out, that older scripts are still working but when I tried to use the exactly same script, it won't compile. So instead of giving my nerves the "last dead kick" I worked around and made a second template for that character.So it works but with a small bug. ;)There are several commands, that are not longer compiling / working since the damned EE... that's one of the reasons why I never touch that Djinni again. Even not for bugfixing, I would have to waste weeks of my time to find out the new commands they used with EE and then write all the older scripts new. That's really not a fun work I want to do in my freetime. :dead:Btw, if you could release your mod without the voices to let us play it yet.... and make a "second edition" later with voices, that would be nice. :whistle:
 
@ HEXENMEISTERProbably you can't compile because SetProfile needs -#include "inc_ai"-. If you look at the aurora log you should see an error about "include recursive inc_musicans". With the patch 1.4 (EE) CDRP added a line in the script "inc_ai" : -#include "inc_musicans"-. And in the file "inc_musicans" there's -#include "inc_ai"-... ;D I solved copying the contents of inc_musicans into inc_ai, deleting -#include "inc_musicans"- from inc_ai and keeping inc_musicans blank with only -#include "inc_ai"-, so if some scripts refer to it, they can find what need. :p... well, a bit repetitive..... ;DTo move characters instantaneously have you tried with SetPositionNearObject? (a waypoint)byep.s. and add the two file "inc_" to the mod!
 
Yes, Nandusso... I know about this "inc_ai" bullshit, after days fumbling around and wondering what Djinni wants from me while saying that "musicans" stuff, I decided to not longer waste my time with this. I saw that there are two "inc_ai" yet, one old and one coming with the EE, but I haven't had time for this (the deadline, you know)and even I had no more nerves (the main reason) to fight the Djinn.Maybe some day CDPR will learn, that they have to make a better toolset and that they have to tell their modders, when they make such changings. For now, I'm waiting for the Open Beta of the Dragon Age Toolset, I just recieved their Newsletter today and hurried to fill in the form. ;DThere will be no more bugfixing to our mod and I won't do a new mod for Witcher, as long as CDPR doesn't make their toolset much user-friendlier (like the DA one maybe ;)) and give the sources or advice to their modders... :wave:
 
Top Bottom