Workaround guide for placing persistent NPC's via Text Edits

From OuroDev
Revision as of 09:14, 4 October 2020 by Zaloopa (talk | contribs) (Added a section describing how to get your map edits into the game.)

Preface

First, this guide is a workaround for the fact that nobody quite has a handle on the proper use of the in-game editor (accessed by typing /can_edit 1 followed by /edit 1 on an account with GM access levels). This workaround isn't intended to be a permanent solution to NPC placement, and may cause unseen issues. For those that are interested in improving this situation, some notes on using the editor are at Map Editing.

Second, this guide assumes that you are running a server setup that can read loose files, as described in Bubble's guide.


Finding the necessary data

With those points out of the way, let's head to where the data lives, shall we?

We will be using Atlas Park for this example, so open the folder serverdata/maps/CityZones/City_01_01 and you will see that there is a file named "Atlas Park is in this dir.txt". This file is simply there to let you know which zone is defined in this particular folder. Most of the City_number_number folders contain a similar file.

The file that we will actually be working with is named "City_01_01_layer_PersistentNPC.txt". Taking a look at the contents of this file will show you a list of NPC's, each of which looks something like this:

Def grp_Henry_Peter_Wong
	Group Omni/_Persistent_NPC
	End
	Flags Ungroupable
	Property	"PersistentNPC"	"Contacts\Level_2\Henry_Peter_Wong.npc"	0
End

That is the initial definition of this persistent NPC, so I'll call it an "initial definition block". It sets flags and properties for the NPC. Note that it does not set the NPC's position or orientation. The block that defines those values is located further down the file, and it looks like this:

Group grp_Henry_Peter_Wong
		PYR 0 89 -0
		Pos 314.499878 175.46051 8.401611
End

I'll call this a "location block" for clarity.

PYR is the orientation (Pitch, Yaw, Roll) of the NPC. As such you'll really only ever see the middle number with a value other than zero, lest the NPC be rotated onto their front/back or left/right sides.

The Pos line is what defines the entity's position on the map. Unfortunately, these coordinates do not quite match up to the in-game coordinates received when using /loc. That's because NPC locations are specified relative to the group containing them, so the coordinates obtained from /loc have an offset baked in. If you scroll back up in the file until you find Laurence Mansfield's location block, you can see the line Def grp_PersistentNPC. That is the start of the block that contains all of the Persistent NPC location blocks.

If you scroll down toward the end of the file, you will see this immediately following James Harvan's location block:

	Property	"Layer"	"PersistentNPC"	0
End

That is the end of grp_PersistentNPC. Immediately afterward, the file ends with the following block, which gives the location for grp_PersistentNPC itself:

Ref grp_PersistentNPC
	Pos -25.999878 -159.365723 -899.901611
End

I'll refer to this last snippet as the "Ref block".


How to choose coordinates

So if you wanted to put your new NPC entity at a known in-game position obtained using /loc, then for each axis, the coordinate in the location block for the individual entity (E) should be set to the desired /loc coordinate on that axis (L), minus the coordinate from the Ref block (R) on that same axis. In other words, for each axis:

	E = L - R

To get to the X-coordinate number in Henry's location block from his in-game X coordinate (L = 288.5), you would subtract the X coordinate in the Ref block (-25.999878). That is, 288.5 - (-25.999878), which simplifies to 288.5 + 25.999878, which equals 314.499878. If you scroll back, you'll see that this matches the first number in his location block. Repeat with the middle Y numbers and then the final Z numbers.

(To go the opposite direction, taking the two values from the "city_01_01_layer_PersistentNPC.txt" file, and obtaining the expected /loc value, you would use L = E + R. 314.499878 + (-25.999878) = 314.499878 - 25.999878 = 288.5

There are a few caveats. First, City of Heroes's coordinate system may give you fits. While increasing Y increases the altitude (as intuition suggests), increasing the X or Z coordinate will move the character west and south respectively (the opposite of what most people would expect).

Second, if you are trying to use the /loc coordinates of an existing entity, it is best if you obtain (or at least double-check) those coordinates in-game or from a demorecord, rather than relying on numbers published on a wiki or forum. Coordinates published on a wiki/forum can be imprecise, and are sometimes entirely wrong in ways that make you question your sanity and/or math skills. (Ask Laurence Mansfield how I know.)

Finally, note that some entities are contained inside multiple nested groups. In the Atlas Park file, you can see that various entities are inside grp_Atmospheric_NPCs and grp_Police_Drones, and then those groups are inside grp_PeresistentNPCs. In these cases, the offset from each of the containers would probably need to be combined.


Practical Example

For our example here, let's make Henry a new (and oddly identical) friend. Copy the initial definition block for the NPC (the first block shown above) and paste it into the City_01_01_layer_PersistentNPC.txt file somewhere near the existing one. Then change the Group name inside to something new (and preferably meaningful). Do the same copy-and-rename for the location block (taking special care to place this second snippet INSIDE the Def grp_PersistentNPC block.)

Lastly, you'll want to change the Pos coordinates of the new location block so that Henry's new friend doesn't spawn directly on top of him (they aren't quite that friendly). Remember, the first number is west/east positioning, the second number controls height, and the third number is south/north positioning.


Your new entries should look something like this:

Def grp_Henry_Peter_Wong_Clone
	Group Omni/_Persistent_NPC
	End
	Flags Ungroupable
	Property	"PersistentNPC"	"Contacts\Level_2\Henry_Peter_Wong.npc"	0
End

and

Group grp_Henry_Peter_Wong_Clone
		PYR 0 89 -0
		Pos 314.499878 175.46051 -1.598389
End

The Pos line above will spawn a copy of Henry standing 10 feet to his north. If you wanted the clone to be standing on the roof of City Hall instead, you could try this line (numbers obtained by going in-game and using /loc while standing at the position, then applying E = L - P):

		Pos 152.499878 264.865723 124.901611


NPC Behavior and Appearance

But I hear you saying, "What if I don't want a useless army of Clones to awkwardly inform me that maybe somebody else has work for me?"

Well then, that's as simple as pointing your new NPC to a different .npc file. NPC files hold pretty much all of the appearance and behaviors for a given NPC. I'm not going to go into detail on how they work here, as that's somewhat out of the scope of this guide. But here's an example where we set the NPC to a Freedom Corps store NPC:

Def grp_Henry_Peter_Wong_Clone
	Group Omni/_Persistent_NPC
	End
	Flags Ungroupable
	Property	"PersistentNPC"	"Contacts\StoreNPCs\FreedomCorp_City_02_02b.npc"	0
End

And, because their behavior is detailed inside the .npc file, this character will actually work as a store! Alright, but what if you want to mark them on the map as being a store? Well this is a pair of propertyies located inside the NPC's Initial Definition block City_01_01_layer_PersistentNPC.txt file, like so:

Def grp_Henry_Peter_Wong_Clone
	Group Omni/_Persistent_NPC
	End
	Flags Ungroupable
	Property	"PersistentNPC"	"Contacts\StoreNPCs\FreedomCorp_City_02_02b.npc"	0
	Property	"miniMapLocation"	"StoreString"	0
	Property	"specialMapIcon"	"Store"	0
End


There are a number of other flags and properties available as well, but I'll leave it to you to explore and figure out those on your own (look around at other NPCs). Finally, it is possible to create an NPC that functions as a custom worktable (it's actually how the merit vendors work) but I will be saving that for a future guide as it is fairly involved.

Getting the edited maps into the game

Once you're done editing maps you'll need to Bin the files, package them in a pigg file, and place them in directories for both the client and the server.  These instructions are written with a test server in mind so they describe manually distributing the pigg file to the client, however if you're running a live production server you'll most likely want to adapt these steps for using a manifest to distribute the pigg file to your players. (Note: all of the below steps are in addition to your normal process after binning.  If you've never created bins before you probably shouldn't start with editing the maps as it's a little more involved!)

Steps:

  • After Binning, your files will be in "data\geobin" on your binning server.  This is the folder you want to package into a pigg file using Piglet.
  • From within the Piglet tool:
    1. Select "File", "New", "Archive from Existing Folder"
    2. Navigate to the "geobin" folder.
    3. Save the new pigg file with any name you like, however it's a good idea to add "Z_" or something similar to the front of the filename to ensure it loads last.
    4. Once the new pigg file is done you'll see its contents in Piglet, ensure that "geobin" is the top level folder on the left, it should look similar to the image on the right.
A .pigg file of the geobin folder.


  • Copy the pigg file into your server's pigg folder and run templates to generate a new vars.attribute file.
  • Copy the pigg file to your client's pigg folder or into a specified patch directory if you have one.

That's it, now your map edits should appear in game!