Jump to content
The Dark Mod Forums
kcghost

View Stealth Score during gameplay?

Recommended Posts

Are there any configuration options or addons available to view your stealth score or breakdown in-game? Or perhaps better, a custom difficulty setting to fail the mission when seen or searched for.

I try to ghost missions, but it can be difficult at times to actually know whether you have been spotted until the end of the mission. Loot stats and other "final" stats would be helpful as well, though I have found "bind "F2" "tdm_show_loot"" to be useful for the purpose of ensuring all loot has been gotten.

  • Like 1

Share this post


Link to post
Share on other sites

I've always wondered why the statistics screen isn't available during the game. More than once I've got to the end of a mission thinking my stealth score is pretty good only to find out that I set off a number of level 4 and 5 alerts.

There is a mission which has a "Don't be seen" objective which I think maps on to level 2 alerts and it is quite interesting the situations that do (and don't) see them off. Sometimes a guard will see you a long way down a corridor and other times that you think are similar, they'll completely ignore you.

  • Like 1

Share this post


Link to post
Share on other sites

I don't think there is an option, though that would be great. It would be good to at least see it in the console feed or something.

What I've done when I've done supreme ghost runs is I've modified the mission to add an objective to not alert an AI to level 1, that way the objective fails every time you get a first alert giving me the feedback. Let me know if you need more information on how to add this if you decide to go with that method. It's possible that there is another way that someone more tech savvy knows about.


My Fan Missions:

   Series:                                                                           Standalone:

Chronicles of Skulduggery 1: Pearls and Swine                     The Night of Reluctant Benefaction

Chronicles of Skulduggery 2: A Precarious Position              Langhorne Lodge

Chronicles of Skulduggery 3: Sacricide [WIP]

 

 

 

Share this post


Link to post
Share on other sites

There is a script command that returns current mission statistics, and it is possible to run scripts from the console. IIRC you need to make an actual script that contains the command and add it to your TDM installation (so it'd be a wesp5-style patch). It can be bound to a hotkey. If there's no more elegant way it can be done like that.

For example:

getMissionStatistic("stealthScore");
 

Entry from the TDM script reference:

scriptEvent float getMissionStatistic(string statisticName);
Returns current mission statistic.
statisticName: Can be one of (case insensitive):
gamePlayTime gameplay time in seconds
damageDealt damage dealt to enemies
damageReceived damage received by player
healthReceived health received by player
pocketsPicked pockets picked by player
foundLoot loot found by player
missionLoot total loot available in mission
totalTimePlayerSeen total time the player was seen by enemies in seconds
numberTimesPlayerSeen number of times player was seen by enemies
numberTimesAISuspicious number of times AI was 'observant' or 'suspicious'. A single AI passing through both alert levels will add 2 to the score.
numberTimesAISearched number of times AI was 'investigating' or 'searching'. A single AI passing through both alert levels will add 2 to the score.
sightingScore sighting score (number of times player was seen * weight)
stealthScore stealth score (sighting score + alerts * weights)
killedByPlayer number of enemies killed by player
knockedOutByPlayer number of enemies knocked out by player
bodiesFound number of times enemies have spotted a body
  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites

Interestingly, this is something that's been missing in a LOT of games that employ stealth, in which your success in ghosting isn't communicated to the player until the end of a mission or a segment. For example in the latest Deus Ex games you'd only know you were totally unseen if you got a "Ghost" bonus popup after finishing a section and the first Dishonored game would only show if you were undetected at the end stats screen of a mission. If you happened to be seen at a distance by a guard but nothing came of it (no combat or further alerts), you'd have no clue until the end.

The only game in recent memory which I've seen have a real-time stats screen of your progress has been Dishonored 2, which can show (among other things) your stealth success and kills/knockouts in your current mission, current play-through and total of all time played.

Share this post


Link to post
Share on other sites

I actually toyed with the Stealth Score code. I got it to go from subtractive to additive. I know how to get it in the sourcecode, so it's an easy task at that level. What I don't know very well is how to get sourcecode variables from the scripting engine, or what one has to do to make it available to it. I feel like it's possible, but I don't know. I think it is a good idea, even if you could just pop it up in the console. It'd let you know mid-game how your ghosting run is going. A mapper could even use it to have in-game events respond to the score, which might be cool. 

Of course the other thing a player can do now is just replicate the stealth score code in a script, since the script has access to all the same events that register the score in the first place. But that's probably overkill anyway. Probably easier just to make the score into a cvar you can get from the console. 

  • Like 1

What do you see when you turn out the light? I can't tell you but I know that it's mine.

Share this post


Link to post
Share on other sites
8 hours ago, Dragofer said:

There is a script command that returns current mission statistics, and it is possible to run scripts from the console. IIRC you need to make an actual script that contains the command and add it to your TDM installation (so it'd be a wesp5-style patch).

I would be glad to add something like this to my TDM Unofficial Patch if somebody would help me implement it in a reasonable way. Like not using the console ;)...

  • Like 1

Share this post


Link to post
Share on other sites
Quote

There is a script command that returns current mission statistics, and it is possible to run scripts from the console. IIRC you need to make an actual script that contains the command and add it to your TDM installation (so it'd be a wesp5-style patch).

Unfortunately it looks like the command getMissionStatistic("stealthScore") doesn't update in real time. I've had the below test script running while a Builder guard was busy looking for or attacking me - it should display my stealth score in the console every 2s, but the console had me believe it was always 0. Same goes for sightingScore, bodies found etc.

Spoiler

void main()
{
	float statistics_stealthScore;

	while(1)
	{	
		sys.wait(2);
		statistics_stealthScore	= $player1.getMissionStatistic("stealthScore");
		sys.println(statistics_stealthScore);
	}
}

 

If the command had worked I could've just made a short script that spawns a message entity and told it to display the current stealth score and other interesting values on a scroll in the corner of the screen, at the push of a button.

What could work is to make a script that adds an objective to avoid all level 1+ alerts, but that's a bit of a hack and it can only tell you that your stealth score is no longer 0. Maybe someone else has other ideas?

Edited by Dragofer
  • Like 1

Share this post


Link to post
Share on other sites

I'd have to go look at the sourcecode again, it's been a while, but it may be that the stealth score isn't actually computed until it's drawn on the stat screen.

My other idea is you could just have a script computing the stealth score the same way it does, by adding value when there are alerts. There are a lot of contingencies it handles though (like how to handle consecutive re-triggers and level-downs), so all of those would have to be copied as well to be the same number. All of it is in the sourcecode though, so you could probably just more or less copy it whole.


What do you see when you turn out the light? I can't tell you but I know that it's mine.

Share this post


Link to post
Share on other sites

@Dragofer  Actually your script works! With *one* tweak, change:

statistics_stealthScore = $player1.getMissionStatistic("stealthScore");

to

statistics_stealthScore = sys.getMissionStatistic("stealthScore");

I don't quite know what I am doing yet as far as the proper way to hook a script like this as an addon pack, but I tried this in mission 1 and saw the values update in real time.

  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, kcghost said:

@Dragofer  Actually your script works! With *one* tweak, change:


statistics_stealthScore = $player1.getMissionStatistic("stealthScore");

to


statistics_stealthScore = sys.getMissionStatistic("stealthScore");

I don't quite know what I am doing yet as far as the proper way to hook a script like this as an addon pack, but I tried this in mission 1 and saw the values update in real time.

For the addon thing, how are you calling the script? Are you using a map script or a object script?  For a addon type of thing imo a https://modwiki.dhewm3.org/Script_object would be a better option, because you can make the object with the script into a prefab that mission makers just put on their maps. 

Share this post


Link to post
Share on other sites

So I made a small project and hosted it on github: https://github.com/kcghost/tdm-ghost

It generates a "tdm_ghost.pk4" that can just be dropped into the darkmod folder, and it works. (Well, it prints the stealthScore to the console every second, not exactly polished).

I am currently hooking it in by overriding tdm_main.script, #including the script there, as well as calling out from tdm_main().

This is all probably terrible, but I'm not familiar enough with this modding ecosystem to know if there is a right way - especially since this is a weird situation. I want the mod to apply to any and all missions, not just those that decide to use it, like the short list of other "addons": https://wiki.thedarkmod.com/index.php?title=Add-ons

So ideally you just drop a pk4 into the darkmod folder and it works, if you don't like it, delete the pk4 (also perhaps configure it with cvars?).

I tried defining a script object and making a def for it, I assumed the init() would be called automatically then but I guess that isn't the case (I guess it would have to be referenced by the map?). And it would have to be #included still anyway as far as I know.

  • Like 1

Share this post


Link to post
Share on other sites
  • Like 1

Please visit TDM's IndieDB site and help promote the mod:

 

http://www.indiedb.com/mods/the-dark-mod

 

(Yeah, shameless promotion... but traffic is traffic folks...)

Share this post


Link to post
Share on other sites

[Obsolete, as the use of a console to call scripts breaks savegames. See this post for a better implementation]

Nice find @kcghost! Usually TDM throws an error message and stops loading if it wants you to use player1 instead of sys or vice versa, so I didn't think to look down that route. Getting rid of that snag has let me make that script, 2-step setup is below:

  1. Create a folder "script" in your /darkmod installation and place this script inside it
  2. Paste the following into TDM's command console: bind "p" "script statistic_message();"

From now on, pressing p while ingame will open a scroll in the corner displaying various stealth statistics: score, suspicions, searches, sightings and bodies found. For bodies, a single body can be found by multiple AIs, adding to the count each time. You can also specify a different hotkey than p in step 2.

 

From a technical side:

  • Normally the optimal way to include a custom script is to use tdm_custom_scripts. However, if an FM provides its own tdm_custom_scripts it'll overwrite the one we changed in the base installation. Therefore I've decided to add this script to an existing .script file (tdm_movers.script), as this won't get overwritten by FM authors and I've deemed this one to have a relatively low chance of being changed in future TDM versions as it just defines 3 entities related to elevators.
  • @HMartDon't think this needs to be a scriptobject as it's meant to be used by players in all missions, not something prefab-like that mappers include with their FMs.
  • Sometimes it looks like it takes a little while before the statistics update (i.e. it looks like points only get added after a search gets called off)
  • The ideal official solution imo would be if the full mission statistics screen could be brought up during a game.

 

Here's the script in text format, as an alternative to downloading the .script:

Spoiler

Create a .txt file named tdm_movers.txt, then change the extension to .script, copy the following text into it and place the file in /darkmod/script:


/***********************************************************************
	The Dark Mod
	Mover-related script objects and methods
***********************************************************************/

#ifndef __TDM_MOVERS__
#define __TDM_MOVERS__

/**
 * greebo: This is a scriptobject representing a MultiStateMoverPosition entity.
 * It provides methods which are called when the MultiStateMover arrives or 
 * leaves this position.
 */
object multistateposition
{
	/** 
	 * greebo: This gets called when the mover arrives at this position.
	 *
	 * @mover: This is the mover entity which has just arrived.
	 */
	void onMultiStateMoverArrive(entity mover);

	/** 
	 * greebo: This gets called when the mover leaves this position.
	 *
	 * @mover: This is the mover entity which has just left.
	 */
	void onMultiStateMoverLeave(entity mover);
};

//---------------------------------------------------------------------------

void multistateposition::onMultiStateMoverArrive(entity mover)
{
	// Add custom script code here
}

void multistateposition::onMultiStateMoverLeave(entity mover)
{
	// Add custom script code here
}


///////////////////////////
//// STATISTIC_MESSAGE ////
///////////////////////////

float	statistic_message_init	= 1;					//lets statistic_message() know it's running for the first time
entity	statistic_message_entity;

void statistic_message_spawn()						//spawns a message entity for thread statistic_message()
{
	statistic_message_entity = sys.spawn("atdm:gui_message");
	statistic_message_entity.setKey("lines", "2");
	statistic_message_entity.setKey("show", "4");
	statistic_message_entity.setKey("force", "1");			//display even if user has disabled popup messages
}

void statistic_message()						//gets mission statistics, applies them to a message entity and shows the message
{
	if(statistic_message_init)					//spawn a message entity if running for the first time
		{
			thread statistic_message_spawn();
			statistic_message_init = 0;
		}
	float	statistic_stealthScore			= sys.getMissionStatistic("stealthScore");
	float	statistic_numberTimesAISuspicious	= sys.getMissionStatistic("numberTimesAISuspicious");
	float	statistic_numberTimesAISearched		= sys.getMissionStatistic("numberTimesAISearched");
	float	statistic_numberTimesPlayerSeen		= sys.getMissionStatistic("numberTimesPlayerSeen");
	float	statistic_bodiesFound			= sys.getMissionStatistic("bodiesFound");

	statistic_message_entity.setKey("text", 
"Stealth score: " + statistic_stealthScore + " Suspicions: " + statistic_numberTimesAISuspicious + " Searches: " + statistic_numberTimesAISearched + " Sightings: " + statistic_numberTimesPlayerSeen + " Bodies: " + statistic_bodiesFound);
	sys.waitFrame();
	sys.trigger(statistic_message_entity);
}


#endif //__TDM_MOVERS__

 

 

  • Like 1
  • Thanks 1

Share this post


Link to post
Share on other sites
51 minutes ago, Dragofer said:

Nice find @kcghost! Usually TDM throws an error message and stops loading if it wants you to use player1 instead of sys or vice versa, so I didn't think to look down that route. Getting rid of that snag has let me make that script, 2-step setup is below:

  1. Create a folder "script" in your /darkmod installation and place this script inside it
  2. Paste the following into TDM's command console: bind "p" "script statistic_message();"

That works great and I would like to include it into my Unofficial Patch with your permission! I have moved the code to it's own sub-script though, as I include a modified tdm_main.script already and this solution is cleaner. But there is still the problem with the redefining of the key, so I thought, could you change the script so that the info is displayed on the mission log page instead? It would make sense too, like you check what objectives are left and how good you are doing!

Edited by wesp5
  • Like 1

Share this post


Link to post
Share on other sites

@wesp5Sure, feel free to include it with your patch. When you say mission log, do you mean the objectives screen, so basically making this show up like an extra objective, the text of which gets updated every x (5?) seconds or when the screen gets brought up?

Adding an extra objective might not be ideal - it's not typical, but also not excluded that FM authors use the same script command to add objectives to their mission, and that could potentially disrupt the objective id's. It'd be something to do at one's own risk.

Ideally I'd like the hotkey to work out of the box (so skip step 2), but all keybinds are stored together in DarkmodKeybinds.cfg and I don't want to overwrite them. Maybe it'd be better to let the script perform the keybind, if that's possible?

Edited by Dragofer
  • Like 1

Share this post


Link to post
Share on other sites
1 hour ago, Dragofer said:

@wesp5When you say mission log, do you mean the objectives screen, so basically making this show up like an extra objective, the text of which gets updated every x (5?) seconds or when the screen gets brought up?

No, this does not need to look like an extra objective. Just an additional line below the objectives title as kind of neutral overview so we don't need an extra key! E.g. like this:

tdm-stats.jpg

Edited by wesp5
  • Like 2

Share this post


Link to post
Share on other sites
9 hours ago, kcghost said:

...

I tried defining a script object and making a def for it, I assumed the init() would be called automatically then but I guess that isn't the case (I guess it would have to be referenced by the map?). And it would have to be #included still anyway as far as I know.

There's ages that I have done anything with scripts so I may be forgetting something but afaik to make object scripts work you need to do this, one create a script file and put it on the script folder, two #include the script file in the main script, in the case of TDM you can include it in, tdm_custom_scripts, like already suggested, then you need to select a entity and give it a spawnarg with, key "scriptobject" value "your_script_object_name", the value is the name of the object inside the script not the name of the script file, is important!

Dragofer, you are right, if this is to be used for all missions than a script object is really not the best option.

Edited by HMart
  • Like 1

Share this post


Link to post
Share on other sites

Thanks so much everybody for all your help with this, this is amazing.

@Dragofer I tried your new script and it looks great!

Also thank you for confirming what I suspected, that there is no entirely clean and elegant way to hook in an addon script. My approach was to include in a "tdm_custom_addon_scripts.script" in tdm_main.script. Perhaps that is a change that should be taken in to the main sources, if any devs are listening. That way "addons" can include themselves in that file, and provide special instructions for simple modifications to work with other addons. Same for executing a "custom_addon_script_init_hook()" from tdm_main(), but I am not sure that is really necessary. Certainly is not necessary for the keybind trigger solution.

 

  • Like 2
  • Thanks 1

Share this post


Link to post
Share on other sites

@wesp5that looks very good as an addon and maybe even as something that can be optionally enabled in the official client with enough polish. The wildest thing I've done with the gui scripting system so far was turning a single-mission briefing into a multi-mission one, so I hardly know yet what I'm doing, but I've identified 5 possibly relevant .gui files (mainmenu_success, mainmenu_objectives, tdm_objectives, tdm_objectives_core, tdm_objectives_defs) and did some early experimentation. I'll describe what I've done in a reproducible manner:

  1. In tdm_objectives_core.gui I duplicated the objectives title & placed it below the original objectives title (windowDef ObjectivesTitle2 and rect 0, 138, 580, 20*SIZE_MULTIPLIER). Now I have 2 titles onscreen.
  2. Lifted a statistics item from mainmenu_success.gui (windowDef StatisticsText1) and put it in a temporary .txt file
  3. Swapped the "text" entry from the statistics item to the duplicated objectives title (#str_07312 replaced by gui::listStatistics_item_0). This is the first item in a list called "listStatistics", defined in main_menu_success.gui. Unfortunately the text no longer shows up.
  4. As a test I inserted: #include "guis/mainmenu_success.gui" before the objectives title. Still no visible text though
  5. Then I inserted: set "cmd" "loadStatistics;"; before the objectives title. Now TDM crashes at startup.

Looks like the problem is with accessing items from listStatistics. Maybe it's because the list is in a different .gui file from the objective .gui files, or the list hasn't been created/loaded/populated yet? Calling @grayman or @greebo as I can see by all the comments that you've both done extensive work with TDM's guis.
 

On another note, actually a scriptobject would be good for my posted script after all - the init function of a scriptobject gets called at mapstart, so that can be used to spawn the message entity. Then the main function can be simplified to do nothing but update spawnargs and trigger the message. Well, what's done is done.

@kcghost thanks, and yes, a way to differentiate between FM-specific scripts and addon scripts would be great and, it looks like, take nothing more than adding an extra line to tdm_main.script and a blank .script file as you suggest. When this thread has come to a close a feature request should be opened in bugs.thedarkmod.com

Edited by Dragofer
  • Like 3

Share this post


Link to post
Share on other sites

This is a cool addition! Cheers for thinking of it!


What do you see when you turn out the light? I can't tell you but I know that it's mine.

Share this post


Link to post
Share on other sites
5 hours ago, Dragofer said:

On another note, actually a scriptobject would be good for my posted script after all - the init function of a scriptobject gets called at mapstart, so that can be used to spawn the message entity. Then the main function can be simplified to do nothing but update spawnargs and trigger the message. Well, what's done is done.

:D I told you 😜 .

Btw I'm recalling that spawnargs can be read and set during gameplay but those changes will not have any immediate effect, you need to respawn the entity again, isn't that true? I'm really not sure. 

Also not sure if this is useful to anyone but looking at the c++ code (in fhdoom engine...but TDM should do the same) the maps scripts are loaded and run by the worldspawn/world entity and you can also call a script function directly on it with the spawnarg key "call" value "function name" (function name should be unique?), the World entity is specially designed to call global functions that need to work during the entire time a map is running, for example idsoftware used it to disable player stamina on all hell levels. Again not sure how useful this is for this discussion tho, just mentioning it. 

Btw you can selected the World entity using DR's entity list window and then you can assign spawnargs to it like any other entity. 

Relevant c++ code for those interested. 

  

// call any functions specified in worldspawn
	kv = spawnArgs.MatchPrefix( "call" );
	while( kv != NULL ) {
		func = gameLocal.program.FindFunction( kv->GetValue() );
		if ( func == NULL ) {
			gameLocal.Error( "Function '%s' not found in script for '%s' key on worldspawn", kv->GetValue().c_str(), kv->GetKey().c_str() );
		}

		thread = new idThread( func );
		thread->DelayedStart( 0 );
		kv = spawnArgs.MatchPrefix( "call", kv );
	}

 

Edited by HMart
  • Like 1

Share this post


Link to post
Share on other sites

Most spawnargs only get read at the start, but some entities read some spawnargs at later points too, i.e. message entities check their text spawnarg each time they get triggered.

33 minutes ago, HMart said:

the World entity is specially designed to call global functions that need to work during the entire time a map is running,

I didn't know that - sounds quite similar to the void main() function actually. It gets run automatically at the start of every map and can be used by mappers to call scripts from the very beginning. Looks like there's some redundancy there - a 3rd method to call at map start is the aforementioned init of a scriptobject.

In terms of scripting it looks to me like we're already sorted. Could maybe refine the script by offering a choice between stealth statistics and other statistics (i.e. loot remaining, knockouts, pickpockets) - but I think the best place to invest further effort into would be to figure out how to show the statistics gui during a running game.

  • Like 1

Share this post


Link to post
Share on other sites
22 minutes ago, Dragofer said:

Most spawnargs only get read at the start, but some entities read some spawnargs at later points too, i.e. message entities check their text spawnarg each time they get triggered.

Thinking about it, imo there's no reason (aside from the engine preventing that) that all spawnargs couldn't be updated in real time, you cannot update them in the def file obviously, because that would trash performance, to do that you need to read/open the def file, change the info, close the file and read it again to use the changed values, very expensive, but while the spawnargs values are in memory, I don't see why you can't just overwrite the info and I assume that is true while the entity exists in the map. 

I didn't know that - sounds quite similar to the void main() function actually. It gets run automatically at the start of every map and can be used by mappers to call scripts from the very beginning. Looks like there's some redundancy there - a 3rd method to call at map start is the aforementioned init of a scriptobject.

The main() function of every map script, is called by the world entity, so in a sense is the same thing, but yes with DoomScript there's many ways to do the same thing, not that is bad imo. 

In terms of scripting it looks to me like we're already sorted. Could maybe refine the script by offering a choice between stealth statistics and other statistics (i.e. loot remaining, knockouts, pickpockets) - but I think the best place to invest further effort into would be to figure out how to show the statistics gui during a running game.

About the gui, I don't really know specifically about TDM gui's, I know the team has changed its code a bunch and perhaps, there's better ways than the Doom 3 way to call and change GUI's now, so, perhaps my suggestions below will be of  no use. Btw In Doom 3 for example, there's no easy way (that I know off) to change the main fullscreen guis with script (like the main menu, the PDA, etc), is all done from the c++ code, but if the gui is called directly from the player (or other entities) then is possible.   

So, If I was making this to the Doom 3 game, If I couldn't make this thought the player entity then I would do like this, first make a new gui then create a new invisible entity or if possible reuse the world entity, just to control the gui. I would then, put on the entity a key "gui" with value "guis/gui_name.gui", then when the player pressed a button, I would trigger the entity itself sys.trigger ($my_entity); this will also trigger immediately any guis "attached" to it.

Inside the gui itself, I would have one or more windowDef's with a text item field, where the value would be updated every frame by a global "gui::parmX" variavel using the script func $my_entity.setGuiParm("parmX", "value");.  Of course is a little more complicated than this but the main idea is there. 

 

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...