Jump to content

The Black Scourge of Candle Cove -- Tchos' development diary


Tchos

Recommended Posts

I found that a special secret object I had hidden in the game did not yet have any worthy loot in it. I put in an item to start a quest, and then made a new placeable treasure generation script that uses the same SoZ loot system I'm using for my creatures. Mainly I wanted to make it so that I could place special quest items on placeables in a variable instead of in the inventory. I also made a generic "mundane treasure" loot table that contains nothing but gems and unenchanted jewelry.

However, I found that it wasn't generating the gold as it should be. After examining the code and trying a few things, I found it was because the function that generates the random gold in kinc_treasure uses a function called GiveGoldToCreature(). Well, this isn't a creature, so I guess that's why it wasn't working. I moved the problematic function into my gp_ script so I could rewrite it and not use the one in the include file, and changed it to CreateItemOnObject(), which worked.

Also gave one of the companions a slightly modified face texture (the lack of eyelashes makes a lot of the vanilla faces look like pasty dolls), and wrote a little of the missing dialogue for the end of the secondary main quest.

I finally found what the problem was with the barbarian companion not gaining XP. I compared his properties to the properties of the other companions, and a noticeable difference was that he had "Designer XP" set to 0, while the others had it set to 1. I don't know how it got set that way, unless I originally made a copy of a blueprint that was set that way.

I added significantly to the journal entries and associated conversation for the secondary main quest, as well as the scripts that update the quest after the event of the main objective. It occurred to me that since there are two quests associated with the same main objective, a player might not take the secondary one until after already accomplishing the objective, which would lead to strange conversations. I made switches for the objective scripts to update the journal to an appropriate stage based on the current quest stage (including the absence of one). The quest is optional, but you'll get one last-chance warning via a journal update if you don't take it before completing the main objective. And if you don't take it, and complete the main objective, no problem. The journal will reflect that.

I'm personally finding it easier to deal with these conversations by using journal conditional checks on almost every main node, rather than using the fall-through method. With as many quest stages in the journal as I'm using, it seems to be less confusing. Just creating journal entries for every possible permutation of the quest. Still, I'm also haphazardly using local variables for some things rather than the journal, like whether an NPC is angry at you, or how much gold you may have negotiated a deal for. And sometimes I'm storing the variables on the NPC (my preference), other times on the PC (when necessary), and sometimes on a waypoint (when the NPC is going to exit the area and respawn elsewhere, which I expect would cause it to lose any variables I changed on it, since it spawns from its blueprint). Whatever works and seems simplest and most foolproof at the time.

I ran through the module one more time, and this time found only very minor things to fix. I haven't been able to test all permutations of the Merchants' League quest, though, so I hope the beta testers can report on any possible oddities there.

I did find that one of the quest markers wasn't updating, and I thought at first it was because I spawned a new copy of the NPC in another area before I destroyed the original, and that was causing the surviving copy to be considered the second instance for the purposes of GetObjectByTag's nth object parameter even though there was only one instance by that time. In fact, it was because I forgot that I assigned a new tag to the second spawned copy, and wasn't searching for that tag.

I also saw that an animated texture wasn't animating, but that could have been a memory issue.

One more scripted event, I think, and I can send this out.

Link to comment
Share on other sites

  • Replies 254
  • Created
  • Last Reply

Top Posters In This Topic

Finally, my teleportation workaround is working. Now the boss fight is working as intended, except that he's not really telegraphing the attack he makes while he's gathering strength, because I thought it was more important to announce the strength-gathering than any particular attack. The combat log shows what attacks he's making for those who want to know.

However, the animated placeables are not animating, as mentioned before. These are waterfall placeables with animated textures. They're quite necessary for the theme of the area and the focus of the boss fight. They were working in earlier tests, and they of course work in the toolset, so I can only guess it's because of the number of effects that appear in this area during the fight.

There's a similar design problem in the area, in that if a character is wearing any gear that emits light, like so many magic items in the OC do, it turns a section of water near-black, rather than the mostly transparent it should be. I never liked all that light-emitting gear in the OC when I was just playing, because I saw it as a waste of an enchantment slot, but since it messes up lighting and water in areas, too, I like it even less.

I'll try replacing some of the animated placeables with visual effects instead. I thought the placeables would be more efficient for the engine's memory, but frozen waterfalls aren't what we need here.

I also had occasion to use SetLocalLocation for the first time, since I needed to put a little failsafe in place to allow the player to loot an important plot item from the body, and that would not be possible if the party killed him while he was in one of the isolated locations. So the death script tells the ipoint regulator what his death location was, and the regulator checks its distance from one of the 3 waypoints, and if he was in one of those locations, then it kicks him (or at least his loot) back onto the plateau.

I redesigned the area with placed effects rather than the animated placeables. I had to modify some effects to get what I wanted. It runs fine in-game, and is arguably an improvement.

I ran through the boss fight once again to make sure it was all functioning properly.

I found the epilogue conversation wasn't firing. I was trying to use the spawned IP Speaker method to start this conversation. I went back and reviewed my earlier entry in this diary where I explained how to get it working. I apparently hadn't finished implementing the script for that part. I did it as I described doing it before, by spawning a second ipoint to act as the conversation owner, and that worked. I think I'll make a wrapper script later to automate this process. The only thing the epilogue is missing is a set of images to use to illustrate what's being described.

So now it can be played completely, from beginning to end, including the epilogue, with all major encounters polished up. As mentioned before, there is a section of the cave that I'm leaving out for the beta test, which is used for a side quest, and it's the only side quest that cannot be completed at this time. I'll inform the beta testers which one it is.

I included a pair of cave tunnels to allow the players to skip through the unfinished cave section, so the cave will seem shorter than it should.

Link to comment
Share on other sites

As I had hoped, the image slideshow function in the conversation nodes, despite saying "TGA to display" don't have to be Targas. Targas are large files. Since the game supports both TGA and DDS files as textures, I tried just putting DDS-compressed images into the slideshow, which are 33% of the file size. It wouldn't allow me to simply select them from the list, but I entered the filenames manually, and they display properly in-game.

 

I filled out the last of the journal entries, added some DM text to the cavern, and set up one of the other companions. I wanted to do another, but I skipped it for the purpose of getting the beta test out.

 

Also did some optimisation, removing items and textures I wasn't using, including some Photoshop files that I shouldn't have saved in the campaign folder. Also converted some larger (1-4MB) TGA texture files to DDS to cut down on disk space usage, and found a whole collection of wall and floor textures I wasn't using, that saved a lot of space when I deleted them.

 

I ran through just a couple of areas to make sure the deletions didn't change anything, but I found a problem with the way the companion dialogues are laid out. Notably, if you don't accept them as companions when you first talk to them, they don't get added to the roster. I added some lines to it with conditions to handle it on one companion, and now I'm adding that to the others.

 

Finished that, after some outside distractions. Fixed a few other last minute things, and now the whole thing's compressing for upload to Dropbox for the beta testers.

Link to comment
Share on other sites

I was quite surprised that after my optimisation efforts, the compressed file size of the whole campaign with all its custom content came to only 145 MB! Maybe I didn't need to remove the uncompressed terrain files from the distribution, but that did save 195 MB (before compression). The uncompressed terrain files are not necessary to play the game, as they're strictly for toolset use. However, they are necessary if you want to open the areas in the toolset, so for the full release I'll be sure to include them separately for those who want them, while those who only want to play can save some space.

 

I've spent the past few days fixing more bugs and omissions in the beta release based on reports, as well as adding or changing certain minor things based on suggestions. I've also been trying to make a Mac-friendly version with Karen's help.

Link to comment
Share on other sites

  • 2 months later...

So, as you can tell, progress slowed significantly since school resumed. There have been days, and even entire weeks, that I did not have the NWN2 toolset open on my computer. Instead, I used most of my free time just playing games. I finished the Heart of Winter expansion of Icewind Dale, for instance, and I just finished playing through the entirety of the main quest of Elder Scrolls 1: Arena. There are a lot of good ideas in some of these games that would be great for including in a NWN2 module, I'd say.

The kuo-toa temple is a bit of a chore to create, with its walkmesh helpers and careful water placement, and it's one of those things that's daunting enough to deter me from starting a modding session. I'll tell you, it was a really tedious choice that I made to individually place hundreds of small rocks along a path, but at least that's done. I also had to retexture them to make them useful for my purposes, and tintable.

Taking care of some smaller items on my to-do list, however, reminded me of the good feelings of modding, so I'm going to plow through the rest of the temple as fast as I can.

One of the small things I did was add a contingency to a particular quest in the cave, so that if you already did certain things before finding the quest, you could still complete it, whereas earlier, if you found the quest too late, you couldn't complete it.

As Andysks referred to in his own diary, I wrote a series of generic quest systems for this module that allow tracking of common quest elements such as collections, manipulation of placeables, triggers, kills, etc. I was partially inspired by the quest system in the Legends plugins, which provides a lot of this kind of functionality, but requires a database as in a PW. My version uses a completely different approach, though, as was necessary since I don't use a database. I offer several options for what kind of object to track these variables on, such as the PC, the module, or a waypoint that will be generated if necessary. It's token-based, which was originally just because I use custom tokens for the journal entries, but it turned out to also be useful for tracking the items. This is because you can have an arbitrary number of different items involved in the system for each quest. (For instance, a simple quest for collecting 5 rocks, or a longer quest for collecting 5 rocks, 10 sticks, 2 leaves, and a mango. However many different objects you want.)

Next I just need to make one for unique item acquisition. I have a quest that would make use of such a script. I think that rather than using the tag-based system, it'll be more efficient to use the user defined slot for the generic system, since with a tag-based script, I'd have to either create a separate script for each item, or give all of the special items the same tag, which would limit further manipulation for those items.

While the scripts for these systems are pretty long and complicated, it's only for the purpose of making them work for a wide range of situations, and produce a wide range of effects. Actually using them makes implementing such quests very simple and quick.

Edited by Tchos
Link to comment
Share on other sites

Today I began scripting the new quest tracker I mentioned in the last post, to streamline the process of making a quest that involves acquiring any number of different types and quantities of items, from any source. A flaw in my plan as stated last time is that items do not have fields in their properties for user defined events or acquisitions. I suppose that's why they came up with the tag-based scripting system in the first place. Still, the fact remains that it would not be efficient, nor match with the method of my other systems in this series to make tag-based scripts for each item, so I modified the module script for acquiring an item to make one extra check in addition to the tag-based check. This was no big deal, since I had already been using a custom module script to add support for Kaedrin's events.

 

I spent my time today researching how to deal with this kind of event, setting up the framework for firing a special script from a variable on the item, and making sure it only fires when the PC or a party member acquires the item, and only once. Ideally, there should be a corresponding addition to the unacquire event, to account for players who like to break things by dropping or selling their quest items before going back to the questgiver, but then again, I'm not sure I care enough to cater to that kind of player. I'll compromise by including an option to have the script set the "plot" and "cursed" flags when you acquire them, which should keep the items on the player until the quest's over.

 

As an aside, it's pretty convenient to have my scripts and other documents in a shared Dropbox folder, because I can work on them when I'm away from my main computer, as I did for these scripts.

 

I tested the bare bones of the script, and it runs only for specially-marked items, and only once, as intended, counting up with a basic counter. Next step is to add the actual tracking code.

Link to comment
Share on other sites

  • 3 weeks later...

I did some more work on the temple. I think it was a bad idea to design it this way, with all the necessary walkmesh helpers, but that part is almost out of the way. I'm glad Bob Hall is doing his part to ease the walkmesh troubles of people in general, but unfortunately my particular aims in this place are just a matter of connecting ramps and planes, not unusual shapes.

 

I also did a little expansion work on my door script, to make sure all standard options are supported, as well as additional door-related options that aren't in the stock game. This is preliminary, and needs more work. It may not appear in this module, at least before I make an expansion.

 

What I think I will include is this city watch set of scripts. I mentioned in a much earlier post that I wanted to use an attacking system like that in Baldur's Gate, where you were not prohibited from attacking enemies until after they delivered their speeches and your party was stripped of buffs and kicked out of stealth and teleported into the middle of the room. That was easy enough, but this also applies to the ability to attack private citizens. Not that you should want to, but you are able to.

 

This required some kind of crime/guard system. I began with a set of NWN1 scripts called Rusty Constabulary, and set about examining them to see what they were doing, how, and why. (This might have been easier if I had actually run the demo module in the game, but I didn't feel like dealing with NWN1.) There were many great ideas in the scripts, and I was able to use large sections of them, though for my purposes, they needed a lot of restructuring. I removed parts, added parts, rewrote things to make them work better in NWN2, and added some familiar voices to certain events.

 

Currently the way it works is that if you attack a private citizen, the citizen will call for help. If a guard is within earshot, he'll come to arrest you. Then you'll have the option of either paying a fine, going to jail, or resisting arrest. The same thing will happen if you steal something from an owned container, and a guard is within range. If you do these things, and a guard is not within range, you'll get away with it. Paying your fine or going to jail both remove your "wanted" status, but you have a permanent record, and every time you commit a crime, they charge you harsher fines.

 

A problem I was having was that the citizens were not fleeing or speaking their strings when you attack them, though they were signalling the guards for help.

 

I decided to write a fleeing script, making the NPC flee the area via the nearest exit waypoint (either one marked as an exit, or just any nearby waypoint) with an ipoint-based control script to cause the NPC to return after some time has passed. Unlike the ForceExit script, I'm putting the NPC into a secret holding cell and making them hidden for the duration of their absence.

 

 

It took a few hours, and a lot of SendMessageToPC feedback to figure out where things were going wrong, but my tests now show the attacked NPC properly fleeing to the nearest exit, with several failsafes in case there are no nearby waypoints with the chosen tag prefix, or if you didn't choose a prefix. Upon reaching the exit, the NPC is whisked away for 4 rounds, and then reappears at the door, walks back to his/her original spot, and breathes a sigh of relief at having survived your unprovoked attack.

 

Meanwhile, of course, the guards are hauling you off to prison, etc. In this demo, the prison and the holding cell are in the same area, so everything is visible and instant, but the idea would be to create appropriate fades to black and delays, and going to a different area for processing, etc. Also, the guards would patrol.

Link to comment
Share on other sites

I resumed work on the kuo-toa temple, and I'm much encouraged by the fact that the walkmesh helper ramps are in fact cooperating with my desire to connect them to raised platforms, also made from walkmesh helpers.

I looked through all the sourcebooks I could find for a symbol of Blibdoolpoolp, but I couldn't find any ready-made images like there were for other gods, like Sune. I needed it for banners / wall hangings. There was one that described the symbol, at least -- a lobster head and a black pearl. So I made one based on that description.

Pic

The topic of creature spawning came up in CF's thread, so I thought I'd show the way I'm doing it. This is a "place it and forget it" type of thing, so I can just put one line in all of my client enter scripts and not have to worry about area variables or specifying any creatures' resrefs in the script itself. Everything it needs to know, it gets from the waypoints themselves, and using a simple waypoint blueprint already marked as "active", it's just a matter of placing it in the area, sticking a creature resref in the waypoint's tag, and copying it around. And any time I want to spawn a different type of creature, I just use a different tag on the waypoint, and don't have to change anything anywhere else. Built for convenience.

// On Client Enter mass creature spawn script, by Tchos.
// Extracted from Tchos' module_inc file.
//
// This function will only work when the caller is an area (as in an area script slot).  
// To use this, just place a waypoint in your area, tagged with the resref of the 
// creature you want to spawn.  Optionally, add a local int saying how many copies
// of the creature you want to spawn.  Will also work if you put the resref of the 
// desired creature in a local variable on the waypoint called "SpawnResref", and 
// the WP tag won't matter.
//
// If you're opting to spawn a group of creatures at a single waypoint, it will name the 
// group by the area's tag + the creature's resref, for any desired later manipulation 
// via group name.

void SpawnCreaturesAtMarkers()
{
	object oArea = OBJECT_SELF;
	int iSpawnActive;
	int iNumToSpawn;
	string sResRef;
	string sTag;
	location lLoc;
	string sGroupName;
	
	// This checks every object of all kinds in the area.
	object oWP = GetFirstObjectInArea(oArea);
	
	while(GetIsObjectValid(oWP))
	{
		// First check to see if this object has the magic variable.
		iSpawnActive = GetLocalInt(oWP, "TchosSpawnActive");
		
		// For each found object, do the following actions only if the object is
		// a waypoint, and has a local int "TchosSpawnActive" equal to 1.
		if(GetObjectType(oWP) == OBJECT_TYPE_WAYPOINT && iSpawnActive == 1)
		{
			// Since it has the magic variable, check to see if it also has any
			// variables indicating what should be spawned, and how many.
			sResRef = GetLocalString(oWP, "SpawnResref");
			iNumToSpawn = GetLocalInt(oWP, "SpawnNumber");
			
			// If number isn't specified, spawn 1.
			if (iNumToSpawn == 0) { iNumToSpawn = 1; }

			// If resref isn't given in local string, then spawn whatever comes after
			// the presumed 3-character prefix in the tag, such as "sp_" (can be anything).
			if (sResRef == "") 
			{ 
				sTag = GetTag(oWP);
				sResRef = GetStringRight(sTag, GetStringLength(sTag)-3); 
			}
			
			lLoc = GetLocation(oWP);
			
			// Different methods depending on whether we're spawning 1 or more than 1.
			if ( iNumToSpawn > 1 )
			{
				// Spawn a group of creatures.
				sGroupName = GetTag(oArea)+sResRef;
				GroupSetSemicircleFormation(sGroupName);
				GroupSetNoise(sGroupName);
				GroupSpawnAtLocation(sGroupName, sResRef, lLoc, iNumToSpawn);
			}
			else
			{
				// Spawn 1 creature.
				CreateObject(OBJECT_TYPE_CREATURE, sResRef, lLoc);
			}

			// All done.  Waypoint no longer active, and will not spawn anything again, unless
			// this variable is set to 1 again.
			SetLocalInt(oWP, "TchosSpawnActive", 0);
		}
		oWP = GetNextObjectInArea(oArea);
	}
	
}
Link to comment
Share on other sites

  • 4 weeks later...

There was actually a bit more to do on the temple stairs, but they're really done now. However, the main floor surface isn't taking light properly. I think the polygons are too big. I'll try tessellating them once and see if that helps.

I completed the general script for updating quests via item acquisition through all methods (picking up from the ground, out of a container, buying from a store, through a placeable, etc. -- any possible way of acquiring the item). Then I modified my placeable-based collection quest script so that it can be used in tandem with this script (some quest items can be gathered from placeables with skill checks, special effects, etc., and others can be obtained from killing creatures or finding them in a box or something).

The key difference that I needed to address was a contingency for the possibility of the player acquiring a quest item before taking the quest. For the placeable version of the script, this is a non-issue, since placeables can be made unusable until the player takes the quest. The way I first imagined this to be used is for the quest items to simply not be there to pick up until the player takes the quest. But I imagine that sometimes you might want to actually begin the quest by finding one of the items, so I added a secondary check -- if you include another variable that allows it, you can start the quest by picking up the item -- with the option of it showing a different journal entry when you do, so that instead of the journal saying that you've acquired one of the items you were searching for, if you weren't actually on that quest yet, it could instead show a journal entry saying you found a curious item that you might want to take to a certain person who you think might be interested, for example.

Anyway, it's another entry in this line of scripts that were complicated to design, just to make it simple to use for all sorts of things. I'm using it for a quest in the cave, which I could have done with more straightforward tag-based scripts, of course, but I wanted to make this system anyway, and why not have a real quest to try it out on? It's like killing a fly with a sledgehammer.

Link to comment
Share on other sites

Factions and faction reputation seems to work differently in this game than I'd expect. It seems to be mostly concerned with whether or not a faction is attackable or will attack the player. While that is a useful function, I want a little finer control, mainly for use in conversations, so my alternate needs would apply only to factions who will not attack the player.

All I really wanted was a simple number to check to see how much a person or the person's associated faction likes you, so that they can offer different things, like access to members-only shops, or access to private areas, or conversely to bar you from amenities that they would normally offer to neutral parties. A simple local int stored on the player will suffice for that, for each faction, and an associated script to add or subtract values from that integer, for use in conversations (such as when you complete quests for a person) or on death events or other relevant actions.

In other news, I've added a few things to the item acquisition script, to provide optional contingencies for the possibility of finding some, or all, of the quest items before receiving the quest from the expected questgiver. Because you don't want the journal entry saying that "You found Mrs Badcrumble's missing pickaxe!" if you never met Mrs Badcrumble in the first place, and didn't know she was missing one. This makes the script more flexible, in case you don't want to use inventory checks, and instead want to do the conditional checks in the conversation entirely with the journal, and if you don't want to use separate tag-based item acquisition scripts for each item.

I went ahead and altered the module unacquire script, too, to add a couple of extra checks, like I did with the module acquire script. One check looks for a variable on the item that specifies a script to run on the event. You might wonder, why not just use a tag-based script? Because if I did that, I would need to either make a separate script to run for each tag, or make all of the tags of a particular set of items the same. This way, I can specify a particular script to run for a whole group of items, and still be able to manipulate them individually by their tags.

In this case, the purpose is to work with my quest tracking script, using the variables on the item that are used for that system, and causing the quest progress to step backward an appropriate amount if the player stops carrying a quest item. Thus, they will get a notification that the journal has been updated, showing that they no longer have the necessary items for the quest. This would also reset the variable on the item that prevents it from advancing the quest again if they pick up the same item again, so that it'll work again. Picking up the item again will restore the quest progress to the way it was before they dropped it. The important thing to emphasise here is that this is for a system that uses custom tokens in the journal, and so it can't be done by checking or manipulating the journal itself.

The other check in the module unacquire script is to look for a variable on the item called "NoDrop". This provides an alternative to the above method, in which the player simply can't drop the item. Thus, any item can be made undroppable by setting that variable.

You might wonder, why not just use the "cursed" flag? Several reasons. First, as has been pointed out here, cursed objects are an irritation to players who still have those items in their inventories after exporting the character and importing it into a new module, where those items are often no longer useful. With this method, since other modules will likely not be using my script, that variable will have no effect because no check will fire, and the object can be dropped in the normal manner.

Secondly, cursed items are a bother because they can't be removed from containers if they're marked as cursed (a workaround is to set the cursed flag once the player takes the item). My script only fires if the item is removed from a member of the player's party, so it can be removed from containers with no problem, but just can't be dropped again.

Likewise, cursed items can't be transferred between party members. This can be bothersome if you want to keep all quest items in one character's inventory, or if they're heavy and you picked them up with a weak character. I couldn't figure out a way of allowing you to drag the item onto a party member's portrait as usual, but I addressed this by allowing the player to target a party member, and dragging the item to the ground, which the script intercepts and transfers the item into the party member's inventory. A "tooltip" explains this process.

This was a somewhat complicated way of creating this final quest, but only because of all the extra frameworks I built to make future quests like this much easier to create. Really, after all this, the most complicated part of the process is the dialogue, and I don't know how to create a script to automate that process.

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
  • Recently Browsing   0 members

    • No registered users viewing this page.

×
×
  • Create New...