Author Topic: Random heads / clothing sometimes invisible (headless)  (Read 327 times)

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« on: April 27, 2014, 10:45:03 am »


               I've been playing around with this well-known engine bug, namely, that dynamic NPCs spawned with random heads and clothing sometimes appear headless, or with invisible body parts (my personal favourite is a pair of shoes that walk around by themselves).

Krevett's solution is good, but not quite perfect. Independently, Henesua posted a script which clears all actions first; I'm not sure why that would affect a newly-spawned creature, but it does seem to make a big difference. Lastly, spawning the creatures 2 seconds after the first PC enters the area improves matters considerably.

Putting that together, OnEntry for the area I have
 
DelayCommand(2.0, zCreateNPC());
where zCreateNPC() uses CreateObject to spawn an NPC from a random template at a random waypoint.

OnSpawn for the creature includes the following snippet:


   Spoiler
   



Not sure this is fool-proof, though; does anyone have a better solution?

Or perhaps someone understands what's happening here? I can't help feeling that piecemeal, empirical solutions like this might not be addressing the root cause in the most effective way.
               
               

               
            

Legacy_kalbaern

  • Hero Member
  • *****
  • Posts: 1531
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #1 on: April 28, 2014, 10:58:25 pm »


               

I've been playing around with NPC randomness for a couple of years now. While on occassion, I still get a glitch appearance-wise, its less than 1% of the time nowadays and most likely to occur when a PC is "sprinting" across a map as NPCs spawn in.


 


Despite any and all optimizations, porting the NPC to Limbo and back, widening encounter and scripted encounter triggers, ... I was still having issues as you have when I was randomizing their heads, hair color, skin color, eye and lip colors along with random names and clothing/armor. After testing with just a few of these features present at a time, I found that creating and equipping new clothing/armor was the single highest chance of glitching the NPCs.


 


Here's how I spawn them nowadays:


 


-Rather than spawn in all NPCs on a given map at once, I now spawn them in from triggers set up similar to a normal encounter and only spawn NPCs in the map sections that a PC is traversing.


 


- NPCs no longer recieve new clothing/armor OnSpawn. Instead, my encounters generate an NPC based on a random calls. When more than one NPC is spawned, I add a small delay (0.5 seconds) between each NPC appearing. Here's a snippet of the code where I spawn in a "couple" based on 1of 4 outfits. I.e., I have 4 different males and females, each with a unique outfit.


CreateObjectVoid(OBJECT_TYPE_CREATURE, "npc_orlbar_m" + IntToString(d4()), lTarget);
DelayCommand(0.5, CreateObjectVoid(OBJECT_TYPE_CREATURE, "npc_orlbar_f" + IntToString(d4()), lTarget));

 


- Randomized names, hair/skin colors and heads are wrapped into a single function and placed at the top of an NPC's OnSpawn Event scripts.


 


- When random weapons are used for "hostile" NPCs, I add a 6 second (1 round) delay before they are forced to equip their weapons.


 


- Avoiding certain clothing/armor appearances (mainly the 1.69 dusters) as options has also aided glitch reduction.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #2 on: April 29, 2014, 08:13:03 am »


               Thanks for that!

I'm having the same problem with the CODI dusters, even though they are toolset-placed and invariant in appearance. I was going to try the Limbo trick on them, as they are just too nice to abandon.

It's a shame that clothing is the main bugbear, as it adds a lot of variety to have one template with lots of costumes to choose from. Perhaps colouring the same outfit dynamically would be faster, though that would mean more templates.

In the past, I've spawned new citizens whenever a player is in the area. However, now I'm finding it easier to keep them, switching off their AI when the area is dormant. They're scripted to exit eventually, spawning someone entirely new elsewhere, to introduce variety.

It always surprises me how random choices produce a fairly high level of duplication (e.g. two identical dresses, one after the other). I might use a "duplication minimisation" algorithm:

Copy list to stack.
Choose random stack element.
Move last stack element into its place.
Decrement stack count.
If stack is empty, replenish from list.

That way, a dress (say) can only occur again once all possible alternatives have been exhausted.
               
               

               
            

Legacy_Thayan

  • Sr. Member
  • ****
  • Posts: 435
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #3 on: April 29, 2014, 02:55:02 pm »


               

I create the NPC in the Oblivion area, and then choose a random location within the area to which they should be jumped. That reduces one of the ActionJumpToLocation calls which can cause a NPC not to equip their clothing. After creating them in Oblivion, I have a looping 1.5 second check to jump them back which at the bottom of this section of code.

 



void RandomizeNPC(object oDynamicNPC, string sMonsterRace = "")
{
  int iGender = GetGender(oDynamicNPC);
  int iRace = GetLocalInt(oDynamicNPC, "RacialType"); //Allow racial overrides to be set on the NPC (see qst_ar_onenter for an example)
  if (!iRace) iRace = GetRacialType(oDynamicNPC);
  if (GetIsPlayableRacialType(oDynamicNPC)) {
    //Select a random appearance (head, default  vs. predefined, clothes, phenotype) for the NPC.
    SetRandomAppearance(oDynamicNPC, iRace, iGender);

    //Next, randomize the clothing for the NPC. This sets the RandomClothing variable on the oDynamicNPC
    SetRandomClothes(oDynamicNPC);

    //Next, set random hair color for the NPC
    DelayCommand(0.3, SetRandomHairColor(oDynamicNPC, iRace));

    //Next, set random skin color for the NPC
    DelayCommand(0.3, SetRandomSkinColor(oDynamicNPC, iRace));

    //Next, set random tattoo colors for the NPC
    DelayCommand(0.3, SetRandomTattooColor(oDynamicNPC));

    //Next, assign a Random Portrait to the NPC
    DelayCommand(0.3, SetRandomPortraitID(oDynamicNPC, iRace, iGender));

    //Next, if not a specified hostile NPC, make sure they are of the commoner faction
    if (!GetLocalInt(oDynamicNPC, "IsHostile")) ChangeToStandardFaction(oDynamicNPC, STANDARD_FACTION_COMMONER);

    //Randomly Adjust Alignment. All standard race templates start at True Neutral
    if (!GetLevelByClass(CLASS_TYPE_PALADIN, oDynamicNPC)) { //Don't adjust paladin alignment
      if (d10() <= 6) AdjustAlignment(oDynamicNPC, ALIGNMENT_EVIL, Random(51)); //Skew more towards evil than good
      else AdjustAlignment(oDynamicNPC, ALIGNMENT_GOOD, Random(51));
      if (d10() <= 5) AdjustAlignment(oDynamicNPC, ALIGNMENT_LAWFUL, Random(51)); //Equal chance they are adjusted to Lawful or Chaos
      else AdjustAlignment(oDynamicNPC, ALIGNMENT_CHAOTIC, Random(51));
    }
  }
  //Else if a non-playable race, just select portraits and various appearances if applicable (gnolls, orcs, goblins, other monsters, etc)
  else {
    if (sMonsterRace == "Goblin") {
      SetCreatureAppearanceType(oDynamicNPC, 82+Random(6)); //Racial appearance types for goblins in nwscript range between 82-87
      SetPortraitId(oDynamicNPC, 219+Random(7)); //Racial portraits for goblins in portraits.2da range between 219-225
    }
    else if (sMonsterRace == "Orc") {
      SetCreatureAppearanceType(oDynamicNPC, 136+Random(6)); //Racial appearance types for orcs in nwscript range between 136-141
      SetPortraitId(oDynamicNPC, 271+Random(4)); //Racial portraits for orcs in portraits.2da range between 271-274
    }
    else if (sMonsterRace == "Gnoll") {
      SetCreatureAppearanceType(oDynamicNPC, 388+Random(2)); //Racial appearance types for gnolls in nwscript range between 388-389
      SetPortraitId(oDynamicNPC, 604+Random(2)); //Racial portraits for gnolls in portraits.2da range between 604-605
    }
  }

  DelayCommand(1.5, GetIsReadyToTransport(oDynamicNPC)); //After a short period of time, check if the NPC is ready to jump back to the playable area
}

This is the looping check with extra Thay-specific stuff in it that you don't need, but hopefully it gives you an idea. This is the safety valve to try and get the NPC out of the inaccessible area.

 



void GetIsReadyToTransport(object oDynamicNPC, int iTimesChecked=0)
{
  if (!GetIsObjectValid(oDynamicNPC)) return; //If not valid, end here

  object oArea = GetArea(oDynamicNPC);
  if (!GetIsObjectValid(oArea)) { //Delay the check by one second if the area is still loading
    DelayCommand(1.0, GetIsReadyToTransport(oDynamicNPC));
    return;
  }

  //If already run once and they are now out of the creation area, end here
  if (iTimesChecked && oArea != GetAreaFromLocation(GetLocation(GetObjectByTag("WP_RandomNPCCreation")))) return;

  //10 iterations and still can't get this NPC out of the creation area - they're not getting out for some reason. Send alert, end here, and destroy them
  if (iTimesChecked > 10) {
    WriteTimestampedLogEntry("NPC ALERT: "+GetName(oDynamicNPC)+"/"+GetTag(oDynamicNPC)+" was stuck in the NPC creation area. It was destroyed.");
    DestroyObject(oDynamicNPC);
    return;
  }

  //Check if they have a regular race appearance. If they do not have clothing equipped, check if there is valid clothing yet created on them and try to equip it, and will check the next heartbeat
  int iEquippingClothes = FALSE;
  int iAppearance = GetAppearanceType(oDynamicNPC);
  if ((iAppearance >= 0 && iAppearance <= 6) && GetItemInSlot(INVENTORY_SLOT_CHEST, oDynamicNPC) == OBJECT_INVALID && GetTag(oDynamicNPC) != "Legionnaire") {
    object oClothing = GetLocalObject(oDynamicNPC, "RandomClothing");
    if (oClothing == OBJECT_INVALID) { //Check if they have template clothing
      oClothing = GetItemPossessedBy(oDynamicNPC, "TemplateClothing");
      SetLocalObject(oDynamicNPC, "RandomClothing", oClothing);
    }
    if (oClothing == OBJECT_INVALID) { //If neither random or template clothing, create something on them
      WriteTimestampedLogEntry("NPC ALERT: "+GetName(oDynamicNPC)+"/"+GetTag(oDynamicNPC)+" did not have random clothing and had nothing in chest slot. Created clothing on them.");
      oClothing = CreateItemOnObject("clothingtemplate", oDynamicNPC);
      SetLocalObject(oDynamicNPC, "RandomClothing", oClothing);
    }

    iEquippingClothes = TRUE;
    DelayCommand(0.3, AssignCommand(oDynamicNPC, ClearAllActions(TRUE)));
    DelayCommand(0.4, AssignCommand(oDynamicNPC, ActionEquipItem(oClothing,INVENTORY_SLOT_CHEST)));
  }

  //Jump the NPC back to the game world
  SetCommandable(TRUE, oDynamicNPC);
  location lNewLoc = GetLocalLocation(oDynamicNPC, "DynamicNPCLocation");
  if (GetLocalObject(oDynamicNPC, "DynamicSpawnArea") != OBJECT_INVALID && GetAreaFromLocation(lNewLoc) == OBJECT_INVALID) lNewLoc = RandomLocation(GetLocalObject(oDynamicNPC, "DynamicSpawnArea"), TRUE);
  if (!iEquippingClothes) DelayCommand(0.5, AssignCommand(oDynamicNPC, ClearAllActions(TRUE)));
  DelayCommand(1.0, AssignCommand(oDynamicNPC, JumpToLocation(lNewLoc)));

  DelayCommand(1.5, GetIsReadyToTransport(oDynamicNPC, iTimesChecked+1));
}

Obviously it's not perfect, and with 1, 3, 5+ second delays the NPCs sometimes magically appear in front of PCs if their random location in the playable area is nearby them. But overall, this has signficantly reduced randomized part-less NPCs in randomized clothing.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #4 on: April 29, 2014, 11:33:26 pm »


               

...I'm having the same problem with the CODI dusters, even though they are toolset-placed and invariant in appearance. I was going to try the Limbo trick on them....



Works a treat.
               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #5 on: February 29, 2016, 08:22:11 am »


               

I've been playing around with this again. I might have solved the problem, or at least reduced it to undetectable frequency in an area flooded with an absurd number of wandering citizens.


 


As previously reported, equipping clothing is a major issue, circumvented by using pre-dressed templates.


 


I also adopted many of the proposals in this thread and others. It's possible that some are unnecessary now, but my rule-of-thumb is "if it ain't bust, don't fix it".


 


The breakthrough was recognizing that changing the head is the second most buggy action. In fact, I found that if you don't change the head, the problem goes away. So I reasoned that changing other aspects of appearance after the head might force correct rendering. The following snippet seems to work reliably:



AssignCommand(oNPC, ActionDoCommand(SetCreatureBodyPart(CREATURE_PART_HEAD, nHead, oNPC)));
AssignCommand(oNPC, ActionDoCommand(SetColor(oNPC, COLOR_CHANNEL_SKIN,     nSkin)));
AssignCommand(oNPC, ActionDoCommand(SetColor(oNPC, COLOR_CHANNEL_HAIR,     nHair)));

The use of ActionDoCommand was recommended in another thread.


 


The bug in SetCreatureBodyPart is, frankly, weird. The most persistent problem appeared to be a male head with an invisible body. Examination of multiple instances showed that in every case the creature was female, with a costume that has no visible male counterpart. Perhaps SetCreatureBodyPart displays the male head and costume first, then gets interrupted before re-rendering as female? Even stranger, the object ID determined in game seems to change when the game is saved. For example, I had a floating head with OID=2443 in game, but OID=9283 when saved (go figure!). I'm certain it was the same instance, because there's an exact match of location, template, skin, hair etc and the instance is unique. On loading, the problem goes away (as it always does).


 


Be that as it may, I'm hoping this is a robust solution, at long last.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #6 on: March 06, 2016, 07:55:26 am »


               

Correction : the bug described above can happen even if the costume has a visible male counterpart.



               
               

               
            

Legacy_Proleric

  • Hero Member
  • *****
  • Posts: 1750
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #7 on: March 06, 2016, 08:58:29 am »


               

It seems that most of the countermeasures described in this thread have value.


 


In particular, Thayan's approach (spawning in Limbo. then jumping to the start point after 1.5 seconds), kalbaern's recommendation to use templates with fixed clothing, and changing the head/skin/hair as per my snippet above, seem to be very effective (in combination) for random dynamic NPCs.


 


Spawning in place, jumping to Limbo, then returning, doesn't work quite so reliably.


 


In the specific case of the CODI dusters, painted in place, I find that ActionJumpToLocation(current location) is sufficient to render them, without jumping to Limbo.


 


More generally, for non-dynamic creatures, I find that encounters work more smoothly if they're painted in Limbo, then jumped in as required, rather than created. For a very few of the hi-poly creatures from the CCC, compiling the models helps considerably, too.



               
               

               
            

Legacy_Grani

  • Hero Member
  • *****
  • Posts: 1040
  • Karma: +0/-0
Random heads / clothing sometimes invisible (headless)
« Reply #8 on: March 09, 2016, 10:43:44 am »


               

It's curious, because I've been spawning various NPCs with random appearances (randomizing heads and creating and equipping clothes in the OnSpawn event) and I have NEVER run into problems such as the ones that are described in this thread. In fact, I wouldn't know about this issue if I hadn't read it here.


 


I use the Community Patch 1.72, but I'm not sure if that's the reason it works flawlessly for me or if it's something else.