The Curious Case of faction "0"

Discussion in 'General Modification' started by marc1967, Mar 12, 2015.

Remove all ads!
  1. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    673
    Likes Received:
    110
    I've been exploring factions as they relate to combat over the last few days and haven't seen a thread about it, other than scattered comments here and there in other threads, so I thought I'd share what I learned. Although at this point I'm sure most of the vet modders know all this instinctively, I'll write it down in case someone like me goes searching. For simplicity, I'll only be talking about the raw faction value (or values) as entered into the npc's proto entry called "NPC Factions", so as to not add the complexity of how it may differ when changed in a mob. Please point out any mistakes and I'll edit as needed.

    There are may uses for the faction value(s), one of which is affecting how a reputation will affect an npc's reaction to the party when they open a dialog with the npc, or when they approach him. This is explained in glorious detail in Ted's modding tutorial http://files.co8.org/others/Shiningted_ToEE_Modding_Tutorials.pdf in the section "Factions, Reactions, and Reputations".

    The use for factions in combat is pretty straightforward. If you attack an npc, any nearby npc that shares a faction number with the attacked npc will join combat against you. (a few might not join, depending on their flags and scripting, but usually they will). So if you attack Elmo in the Inn in Hommlet, whose faction setting is "9, 12" (9 = Hommlet villager, 12 = Old Faith believer), any other Hommlet villager or Old Faith Believer will join in. The faction values don't need to match exactly, they just need to share one value. So a traveling merchant staying at the Inn who happens to be an Old Faith believer and has factions of "12, 13, 26, 59" will join the fight since he has at least one matching value with Elmo; in this case "12".

    Ok, moving on to faction "0". First, I am making an assumption that a proto with a blank faction entry defaults to faction 0. After some testing this seems to be the case, but I can't be 100% sure. So when I refer to an npc with a faction of 0, it should apply to all protos with no factions entered too.

    Npc's with faction "0" can be thought of a "neutral" or "isolated". It's not really a faction like the others, but more of a behavior, and can get a little convoluted at times.

    - If you attack an npc with faction "0", no other npc will respond or join in combat, unless scripted somewhere.

    Attacking Furnok at the Inn, who is an npc with faction "0", will provoke no response from anyone else, even others of faction "0". Hack away, then chat it up with Gundygoot while Glora deals with the body. Actually, I think he adds to your Butcher of Hommlet reputation in his san_dying(), but this is not because of his faction. ​

    - If multiple npcs of faction "0" are dragged into the same combat, they will not attack each other. Whereas...

    The normal behavior is that if two (non-party) npcs are in the fight, they will consider anyone who doesn't have a faction matching one of their own as a valid enemy to target. For example, if Elmo with "9, 12", and Lord Wilfrick with "31, 36" are in the same battle (but not in the party), they will attack each other as likely as they are to attack you. In fact, if you back away and their AI is set to attack closest, they will just fight it out together as you stand back and watch.​

    - But adding "0" as a faction in combination with other factions will not count as a matching faction, even though it seems to in what I wrote in the previous bullet.

    For example, giving factions "0,9,12" to Elmo, and "0,31,36 to Wilfrik will not trigger as matching factions. They will still attack each other in combat as before. ​


    Please add to this if you know any more.


    On a related topic, does anyone know if the proto value of Category (per category.mes) has any effect in game? This is not to be confused with the Monster Category (mc_type_undead,etc.).
     
  2. Rudy

    Rudy Established Member

    Joined:
    Jan 30, 2005
    Messages:
    345
    Likes Received:
    2
    Very interesting; thanks for the effort.

    Is it the presence of faction "0" that prevents others from reacting when you attack Furnok, or is it simply the lack of other matching factions that prevents them from reacting? If it is the latter, then it seems that 0 is exactly what you would expect the lack of faction to be, no?
     
  3. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    673
    Likes Received:
    110
    Good question.

    No one will ever match his faction because all he has is "0", so he'll never match up with any other faction (as they are > 0), and another "0" faction won't help him because they don't help each other.

    To really answer that question, I would need to see the how the code handles this internally Without seeing the logic, having a faction of "0" could process two different ways:

    1) The list with his faction numbers is empty.

    or...

    2) His faction number is "0", and certain rules are applied to that faction.
     
  4. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,038
    Likes Received:
    42
    I don't think it does. AFAIK it's just a way of categorizing protos, although the application is willy-nilly. (Humanoids, Homlett, etc.) Most useful for world building/mob placement, no doubt.

    [edit]

    I'm guessing the first, since 0 is referred to as "the empty faction."
     
  5. Rudy

    Rudy Established Member

    Joined:
    Jan 30, 2005
    Messages:
    345
    Likes Received:
    2
    Are there any cases where an NPC has faction 0 AND other factions? If not, I would bet a lot on "0" just being a placeholder for 'no faction', and nothing more.
     
  6. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    673
    Likes Received:
    110
    I see about ten of them in protos.tab, but they look like they are experiments that didn't work out and got left there, so you are probably right.
     
  7. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    The faction field is a 32-bit fixed length array (up to 50 entries allowed).

    From what I see in the code, '0' is used as a terminator, e.g. function 1007E430 Obj_Faction_Has will return 0 if it runs into faction '0'. So in that sense it's like a fixed-length string.

    I think having both faction 0 and other faction could have unintended consequences... and it may also depend on the order you input them. E.g. in your elmo + Wilfrik experiment, what happens if you put the zero at the end of their factions? Do they still fight one another? I suspect they won't.
     
  8. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    673
    Likes Received:
    110
    I agree. The main conclusion I've come to is just to leave any protos with no factions as a blank entry (instead of "0"), and don't mix the "0" in with the other numbers. Plus I spent an hour just watching 10 Elmos and 5 Wilfrick duke it out repeatedly in a battle royale, hehe.

    They still fought each other whether they were "0 9 12" or "9 12 0" or "9 0 12". Trying all the different permutations along with who attacked first and things like that is very time consuming, so I think my experiments are over.

    I assume the code hasn't been fully cracked as to how to access the factions in a script. I saw some threads earlier where you and ted were talking about trying to access and change obj_f_npc_faction, but the numbers came out in the hundreds sometimes, and completely similar protos spawned would increment that number by 1 or 2 with each new spawn, so there must be some other funny stuff happening with that value.
     
  9. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,038
    Likes Received:
    42
    Were you testing the Elmo-Wilfrick battles from a save? Just to add to the fun, it's possible the original data is saved in saves. (Certainly wouldn't be the first time ToEE has pulled something like that.)
     
  10. marc1967

    marc1967 Established Member

    Joined:
    Jan 19, 2014
    Messages:
    673
    Likes Received:
    110
    I learned a while back, probably from you early last year, that doing anything from a save can have consequences like that, so I start everything I test now with a fresh game. It's about a 30 second process to start the game and pick new characters and then type "from marc import *" every time I test something, and I probably waste a lot of time doing that, but at least I don't get deceptive results from lingering conflicts.

    From there I just have a quick function called cob(), that spawns any npcs.

    Actually, there are a few things I test without reloading. Mesh and map art changes (though not sector data) seem to refresh automatically when you save the new jpg or tga.
     
  11. Gaear

    Gaear Bastard Maestro Administrator

    Joined:
    Apr 27, 2004
    Messages:
    11,038
    Likes Received:
    42
    Cool. :thumbsup:

    The most horrifying example of that I think is secret doors. Not just the bug that requires you to delete all saves if you get it, but simply changing the DC of a secret door requires a new game to test if characters are detecting the new setting or not.
     
  12. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    It's not funny stuff, it's just that the value you get with obj_get_int is a pointer :)

    Once again obj_f_npc_faction is a non-32bit field, but rather a list field (each entry is 32bit). That means the property stored in there is a memory pointer. There's code in the DLL to manipulate it obviously, but it needs to be bound to a python command.

    Come to think of it, maybe the easiest thing to do is create a python caller and let you access the functions directly... hmm...
     
  13. Shiningted

    Shiningted I changed this damn title, finally! Administrator

    Joined:
    Oct 23, 2004
    Messages:
    12,740
    Likes Received:
    374
    Errr, accessing factions by script (so we can scriptingly remove certain folks from combat as necessary) been a major wishlist thing for a long time, if you feel like doing it..?
     
  14. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Oh I definitely want to do that. I'm still largely in "analysis mode" right now, but it's the first item on the agenda for adding new functionality (general manipulation of non-32bit fields, that is, where the factions are a private case).

    BTW, for PCs you can currently play around with this using reputations. (I'm not sure you can directly add factions to PCs at all, actually, other than this method)
     
  15. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,622
    Likes Received:
    538
    Coming soon to a ToEE near you...

    [​IMG]

    Brother Smyth, just because I unload all my junk to you is no reason to join the Temple!

    [​IMG]

    generated by the following code:
    Code:
    static PyObject * pyObjHandleType_Faction_Has(TemplePyObjHandle* obj, PyObject * pyTupleIn){
    	int nFac;
    	if (!PyArg_ParseTuple(pyTupleIn, "i", &nFac)) {
    		return nullptr;
    	};
    	return PyInt_FromLong(templeFuncs.Obj_Faction_Has(obj->objHandle, nFac));
    };
    
    static PyObject * pyObjHandleType_Faction_Add(TemplePyObjHandle* obj, PyObject * pyTupleIn){
    	int nFac;
    	if (!PyArg_ParseTuple(pyTupleIn, "i", &nFac)) {
    		return nullptr;
    	};
    	
    	if (nFac == 0){
    		return PyInt_FromLong(0);
    	}
    
    	templeFuncs.Obj_Faction_Add(obj->objHandle, nFac);
    	return PyInt_FromLong(1);
    };
    
    static PyMethodDef pyObjHandleMethods_New[] = {
    	"faction_has", (PyCFunction)pyObjHandleType_Faction_Has, METH_VARARGS, "Check if NPC has faction. Doesn't work on PCs!",
    	"faction_add", (PyCFunction)pyObjHandleType_Faction_Add, METH_VARARGS, "Add a faction to an NPC. Doesn't work on PCs!",
    	0, 0, 0, 0
    };
    
    
    
    PyObject* __cdecl  pyObjHandleType_getAttrNew(TemplePyObjHandle *obj, char *name) {
    	LOG(info) << "Tried getting property: " << name;
    	if (!_strcmpi(name, "co8rocks")) {
    		return PyString_FromString("IT SURE DOES!");
    	}
    
    	if (!_strcmpi(name, "ObjHandle")) {
    		return  PyLong_FromLongLong(obj->objHandle); 
    	}
    
    	if (!_strcmpi(name, "factions")) {
    		ObjHndl ObjHnd = obj->objHandle;
    		int a[50] = {};
    		int n = 0;
    
    		for (int i = 0; i < 50; i ++){
    			int fac = templeFuncs.Obj_Get_IdxField_32bit(ObjHnd, obj_f_npc_faction, i);
    			if (fac == 0) break;
    			a[i] = fac;
    			n++;
    		};
    
    		auto outTup = PyTuple_New(n);
    		for (int i = 0; i < n ; i++){
    			PyTuple_SetItem(outTup, i, PyInt_FromLong(a[i]));
    		};
    
    		
    		return  outTup; 
    	}
    
    	if (!_strcmpi(name, "faction_has")) {
    		return Py_FindMethod(pyObjHandleMethods_New, obj, "faction_has");
    	}
    	else if (!_strcmpi(name, "faction_add"))
    	{
    		return Py_FindMethod(pyObjHandleMethods_New, obj, "faction_add");
    	} 
    	else if (!_strcmpi(name, "substitute_inventory"))
    	{
    		ObjHndl ObjSubsInv = templeFuncs.Obj_Get_Substitute_Inventory(obj->objHandle);
    		return templeFuncs.PyObj_From_ObjHnd(ObjSubsInv);
    	};
    	
    
    	if (!_strcmpi(name, "obj_get_field_64")) {
    		
    		return nullptr; 
    	}
    
    	return pyObjHandleTypeGetAttr(obj, name);
    }
    
    
    
    
    int __cdecl  pyObjHandleType_setAttrNew(TemplePyObjHandle *obj, char *name, TemplePyObjHandle *obj2) {
    	LOG(info) << "Tried setting property: " << name;
    	if (!strcmp(name, "co8rocks")) {
    		return 0;
    	}
    
    	if (!strcmp(name, "substitute_inventory")) {
    
    		if (obj2 != nullptr)  {
    			if (obj->ob_type == obj2->ob_type){
    				templeFuncs.Obj_Set_Field_ObjHnd(obj->objHandle, obj_f_npc_substitute_inventory, obj2->objHandle);
    			}
    		}
    		return 0;
    	}
    
    
    	return pyObjHandleTypeSetAttr(obj, name, obj2);
    }
    	}
    "Waaaaidaminute", I hear you say, "C++ code in my ToEE?!" Ya better believe it :yes:
     
    Last edited: Mar 17, 2015
Our Host!