Jump to content
The Dark Mod Forums

Script: Per limb damage, skill system (WIP)


Recommended Posts

After some amount of work I'm happy to be able to share my Christmas gift for TDM! Or at least half of it, considering the other half is still only in design phase.

I created an addon that implements detailed player functionality, inspired by the first DeusEx game (The Conspiracy). It's NOT a mission script but an addon, meaning you place the pk4 in your TDM directory to enable the system and it will automatically work in each and every FM. Note that due to using tdm_user_addons.script / user_addon_init() it may disable or get disabled by other addons you have installed... this is a design limitation which can hopefully be lifted at some point in the future. This plugin will be included in my cyberpunk total conversion The Dark Module and automatically active with it, but first I shall design it and make it available as a small independent addon for vanilla TDM.

In the current initial release it implements just per-limb damage; The upcoming plan is to add a skill / augmentation system, allowing the player to use loot as skill points to enhance various body parts and gain new or improved abilities. Due to the scripting system being very limited in what it lets me modify, I'm still gathering documentation on how to implement those skills and which I can have. So until then detailed body damage with penalties remains the only part that's theoretically finished so far (further improvements are required here too)... including a HUD component above the lightgem showing the status of each body part: Green = full health, yellow = half health, red = (close to) no health, black = no health left.

testing_2020-12-24_23_08_22.jpg.9261d13ceb319f0a39c6323b2bf91e91.jpgtesting_2020-12-24_23_17_26.jpg.e9fbccfbfe208bfa7a8ec9b0022eb7a7.jpgtesting_2020-12-24_23_17_49.jpg.2fca4c1fe7bf8d7bc4e52a9281ce6afe.jpgtesting_2020-12-24_23_18_02.jpg.663257857ae7aaf498f3b3ed3b5e3c1f.jpg

The individual limbs available: Head, Torso, Left Arm, Right Arm, Left Leg, Right Leg... arms and legs work in unity however. They each take damage with the player as well as healing together with them. The more damaged a group is, the more a respective penalty is applied to the player. Groups and penalties include:

  • Head: When the head is damaged, the player begins to get dizzy and has their vision impaired. Currently the only effect replicates the flashbomb, causing white dots to appear around the player and disrupt their view until the head is healed. As the player can't live without a head, reaching 0 will cause instant death. More effects are possible and pending.
  • Torso: Damage to the torso translates to damage to the cloak, increasing the player's lightgem and rendering them more visible even in dark spots. As the player can't live without a torso, reaching 0 will cause instant death. Given script limitations I'm unable to simulate lung damage and decrease / limit the amount of air the player has.
  • Arms: Arm damage makes it difficult for the player to hold items: In-world objects being held will probabilistically get dropped, more often the worse your arms are hurt. When both arms reach 0 health, the player can no longer pick up anything in the world without instantly dropping it... you also become unable to use any weapons. Due to limitations in the scripting system, I'm unable to decrease the speed or accuracy of the blackjack / sword / bow as was desired.
  • Legs: As expected leg damage will make the player walk more slowly. It was desired that when one leg is lost the player can no longer jump, whereas when both legs are gone you remain stuck in crouch mode until healed... due to limitations in the scripting system this part is also not possible at the moment.

A crude limitation is the fact that limb damage is primarily based on the direction the player is walking toward... for example, increased likelihood of suffering damage to your right arm and leg if strafing right the moment you take the damage. The script system doesn't let you extract the vector direction of the last damage event, thus I can't use the real direction the hit came from when calculating which body part should absorb the most health loss. This means that even if an arrow comes from above and hits the player's head area, the player will only take damage to the legs if they're falling downward the moment they got hit... for the time being this provides a bare minimum amount of realism but is a very bitter implementation.

For this reason it would be greatly appreciated if any of the code developers could join this discussion and verify if they can help with adding the necessary hooks to external scripts: With 2.09 getting periodic beta releases at this point in time, it would be a great opportunity to make changes to the builtin player script that allow an external function to modify more player variables. This includes the efficiency of weapons, if the player is allowed to jump or forced to always crouch, and I'd also really appreciate a hook to manipulate the breath so air can be lowered as if the player is underwater. I understand other priorities exist or if the work may be considered too much, however this would help in being able to finish this mod with the proper functionality and planned skill set.

In the meantime let me know what you think of this idea and how I went about it! So far no new assets are included except the GUI graphics: Everything is done with less than 250 lines of script which I'd say is a good achievement :) I've attached the pk4 and since it's so lightweight I'll also add the main script straight in this post.

player_damage_1.0.pk4

#define DAMAGE_WAIT 0.0166667

#define EXPOSURE_ARM_LEFT 2
#define EXPOSURE_ARM_RIGHT 2
#define EXPOSURE_LEG_LEFT 2
#define EXPOSURE_LEG_RIGHT 2
#define EXPOSURE_HEAD 3
#define EXPOSURE_TORSO 1

#define PENALTY_TORSO_LIGHTGEM 4

player self;
float damage_gui;
boolean dizzy;
entity dizzy_particles;

float bound(float val, float min, float max)
{
	if(val < min)
		return min;
	if(val > max)
		return max;
	return val;
}

// Range based probability: Calculates a probability per frame independent of wait time (0 - 1 range at 1 chance per second)
boolean prob(float x)
{
	return sys.random(1) > x && sys.random(1) < DAMAGE_WAIT;
}

// Directional exposure calculator
float dex(vector dir, float ex_front, float ex_back, float ex_right, float ex_left, float ex_up, float ex_down)
{
	float maxvel = 100;
	float dir_front = bound(dir_x / maxvel, 0, 1) * ex_front;
	float dir_back = bound(-dir_x / maxvel, 0, 1) * ex_back;
	float dir_right = bound(dir_y / maxvel, 0, 1) * ex_right;
	float dir_left = bound(-dir_y / maxvel, 0, 1) * ex_left;
	float dir_up = bound(dir_z / maxvel, 0, 1) * ex_up;
	float dir_down = bound(-dir_z / maxvel, 0, 1) * ex_down;
	return dir_front + dir_back + dir_right + dir_left + dir_up + dir_down;
}

void player_damage_update_arm(float dmg_l, float dmg_r)
{
	float hl_l = self.getFloatKey("health_arm_left");
	float hl_r = self.getFloatKey("health_arm_right");
	float hl = (hl_l + hl_r) / 2;

	if(dmg_l != 0 || dmg_r != 0)
	{
		hl_l = bound(hl_l - dmg_l, 0, 1);
		hl_r = bound(hl_r - dmg_r, 0, 1);
		hl = (hl_l + hl_r) / 2;
		self.setKey("health_arm_left", hl_l);
		self.setKey("health_arm_right", hl_r);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemArmLeft", hl_l);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemArmRight", hl_r);

		// Penalty #1: Disable the weapon once the arm are damaged to minimum health
		if(hl == 0)
		{
			self.selectWeapon(WEAPON_UNARMED);
			self.disableWeapon();
		}
		else
		{
			self.enableWeapon();
		}
	}

	// Penalty #2: Probabilistically drop held items based on arm damage
	if(hl == 0 || prob(hl))
		if(self.heldEntity() != $null_entity)
			self.holdEntity($null_entity);
}

void player_damage_update_leg(float dmg_l, float dmg_r)
{
	float hl_l = self.getFloatKey("health_leg_left");
	float hl_r = self.getFloatKey("health_leg_right");
	float hl = (hl_l + hl_r) / 2;

	if(dmg_l != 0 || dmg_r != 0)
	{
		hl_l = bound(hl_l - dmg_l, 0, 1);
		hl_r = bound(hl_r - dmg_r, 0, 1);
		hl = (hl_l + hl_r) / 2;
		self.setKey("health_leg_left", hl_l);
		self.setKey("health_leg_right", hl_r);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemLegLeft", hl_l);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemLegRight", hl_r);

		// #Penalty #1: Make movement slower
		self.setHinderance("health", 0.25 + hl * 0.75, 1);
	}
}

void player_damage_update_head(float dmg)
{
	float hl = self.getFloatKey("health_head");
	float time_current = sys.getTime();

	if(dmg != 0)
	{
		hl = bound(hl - dmg, 0, 1);
		self.setKey("health_head", hl);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemHead", hl);

		// Penalty #1: Without a head the player dies
		if(hl == 0)
			self.damage(self, self, self.getOrigin(), "damage_suicide", 1);

		// Penalty #2: Simulate dizzyness starting at half health
		if(hl <= 0.5)
		{
			if(!dizzy)
			{
				dizzy_particles = sys.spawn("func_emitter");
				dizzy_particles.setModel("flashbomb.prt");
				dizzy_particles.setOrigin(self.getEyePos());
				dizzy_particles.bind(self);
				dizzy = true;
			}
		}
		else
		{
			if(dizzy)
			{
				dizzy_particles.remove();
				dizzy = false;
			}
		}
	}
}

void player_damage_update_torso(float dmg)
{
	float hl = self.getFloatKey("health_torso");

	if(dmg != 0)
	{
		hl = bound(hl - dmg, 0, 1);
		self.setKey("health_torso", hl);
		self.setGuiFloat(damage_gui, "PlayerDamage_ItemTorso", hl);

		// Penalty #1: Without a torso the player dies
		if(hl == 0)
			self.damage(self, self, self.getOrigin(), "damage_suicide", 1);
		// Penalty #2: Torso damage negatively affects the lightgem
		self.setLightgemModifier("damage", (1 - hl) * PENALTY_TORSO_LIGHTGEM);
	}
}

void player_damage()
{
	sys.waitFrame();

	self = $player1;
	damage_gui = self.createOverlay("guis/player_damage.gui", 1);
	float health_old = 100;

	// Init by sending a heal event filling the limbs to full health
	player_damage_update_arm(-1, -1);
	player_damage_update_leg(-1, -1);
	player_damage_update_head(-1);
	player_damage_update_torso(-1);

	while(1)
	{
		// sys.waitFrame();
		sys.wait(DAMAGE_WAIT);

		float health_current = self.getHealth();
		float dmg = (health_old - health_current) / 100;
		float dmg_arm_left = dmg * EXPOSURE_ARM_LEFT;
		float dmg_arm_right = dmg * EXPOSURE_ARM_RIGHT;
		float dmg_leg_left = dmg * EXPOSURE_LEG_LEFT;
		float dmg_leg_right = dmg * EXPOSURE_LEG_RIGHT;
		float dmg_head = dmg * EXPOSURE_HEAD;
		float dmg_torso = dmg * EXPOSURE_TORSO;

		// If this is damage and not healing, apply directional damage to each limb
		if(dmg > 0)
		{
			// Currently we estimate damage direction based on the player's velocity, we should fetch the real direction of a damage event when this becomes possible
			vector dir = self.getMove();
			vector ang = self.getViewAngles();

			// Protections based on the player's position and relation to the environment
			// protection_look: 1 when looking up, 0 when looking down
			// protection_low: Higher as the lower part of the body is exposed
			float protection_look = 1 - (90 + ang_x) / 180;
			float protection_low = 1;
			if(self.AI_CROUCH)
				protection_low = 0;
			else if(self.AI_ONGROUND)
				protection_low = 0.75;

			// Use the dex function to calculate directional exposure patterns, direction order: Front, back, right, left, up, down
			// Arms: Somewhat likely to be hit, no added protection
			// Legs: Somewhat likely to be hit, added protection when the player is crouching
			// Head: Unlikely to be hit, added protection when the player is looking down
			// Torso: Likely to be hit, no added protection
			float exposure_arm_left = bound(sys.random(0.375) + dex(dir, 0.5, 0.25, 0.0, 1.0, 0.0, 0.25), 0, 1);
			float exposure_arm_right = bound(sys.random(0.375) + dex(dir, 0.5, 0.25, 1.0, 0.0, 0.0, 0.25), 0, 1);
			float exposure_leg_left = bound(sys.random(0.375) + dex(dir, 0.75, 0.5, 0.0, 0.5, 0.0, 1.0) * protection_low, 0, 1);
			float exposure_leg_right = bound(sys.random(0.375) + dex(dir, 0.75, 0.5, 0.5, 0.0, 0.0, 1.0) * protection_low, 0, 1);
			float exposure_head = bound(sys.random(0.25) + dex(dir, 0.25, 0.75, 0.5, 0.5, 1.0, 0.0) * protection_look, 0, 1);
			float exposure_torso = bound(sys.random(0.5) + dex(dir, 0.75, 1.0, 0.0, 0.0, 0.0, 0.0), 0, 1);

			// Apply the exposure to damage, multiplied to simulate the sensitivity / resistance of each limb
			dmg_arm_left = exposure_arm_left * dmg * EXPOSURE_ARM_LEFT;
			dmg_arm_right = exposure_arm_right * dmg * EXPOSURE_ARM_RIGHT;
			dmg_leg_left = exposure_leg_left * dmg * EXPOSURE_LEG_LEFT;
			dmg_leg_right = exposure_leg_right * dmg * EXPOSURE_LEG_RIGHT;
			dmg_head = exposure_head * dmg * EXPOSURE_HEAD;
			dmg_torso = exposure_torso * dmg * EXPOSURE_TORSO;
		}

		player_damage_update_arm(dmg_arm_left, dmg_arm_right);
		player_damage_update_leg(dmg_leg_left, dmg_leg_right);
		player_damage_update_head(dmg_head);
		player_damage_update_torso(dmg_torso);

		health_old = health_current;
	}
}

 

Edited by MirceaKitsune
Fix pk4
  • Like 3
Link to post
Share on other sites

Will this be an optional playing feature? I understand it's only in effect if the pk4 is present, but being able to toggle it in-game would be a good option; changing the presence of the pk4 could be considered too much work.

Movement and "use" restrictions might impact released missions. I.e. you need to be able to complete a certain movement to continue the mission, and not being able to do that fails the mission.

Someone down the road might suggest a "weight" limit: how much you can carry as your limbs become weaker. A reasonable request. But missions could fail if TDM suddenly pays attention to weight, and you must obtain X items in order to be successful, but you can't due to getting hurt and losing strength.

 

Link to post
Share on other sites
28 minutes ago, grayman said:

Will this be an optional playing feature? I understand it's only in effect if the pk4 is present, but being able to toggle it in-game would be a good option; changing the presence of the pk4 could be considered too much work.

Movement and "use" restrictions might impact released missions. I.e. you need to be able to complete a certain movement to continue the mission, and not being able to do that fails the mission.

Someone down the road might suggest a "weight" limit: how much you can carry as your limbs become weaker. A reasonable request. But missions could fail if TDM suddenly pays attention to weight, and you must obtain X items in order to be successful, but you can't due to getting hurt and losing strength.

 

Not yet but I can see how that would be a good idea. I can use sys.getcvar() to read any setting correct? It should be a piece of cake to make it more configurable and possible to toggle then, should do this for the next update. In the meantime this is experimental anyway, so the pk4 is recommended for players who wish to toy with the concept.

Link to post
Share on other sites

The next update is here! I consider the damage component as finished as it gets, it should be mainly bug fixes and minor tweaks from here. I polished everything and restructured much of the code, and can now recommend players to install and enable this during everyday gameplay. Fixes and improvements include:

  • Better patterns and algorithms for correctly distributing damage to the proper limbs.
  • New protection: Holding an object in your hands exposes the arms more but the torso less.
  • New penalty: Mild turn hindrance while the head is damaged.
  • Values from the previous call are used when determining player velocity, position, shape. This offers safer and more realistic results... for example fall damage will distribute accurately as the player's velocity before hitting the ground is calculated, rather than reading it afterward when the player is standing still.
  • As suggested by one of the developers, cvars can now be used to adjust the system. Two cvars were added: "g_player_damage" scales the damage limbs take from ordinary damage, can be changed in-game with immediate effects. "g_addon_player" disables the addon entirely and won't load it, however it only has an effect before map load meaning the mod won't be loaded / unloaded in realtime. Not setting a cvar is the equivalent of setting it to 1, you must set it to 0 to disable.
  • External mods can work with limbs via getFloatKey() and setKey(). For example: $player1.getFloatKey("health_torso") or $player1.setKey("health_arm_left", 0.5).
  • Each limb will now flash on the HUD when taking damage, making it obvious which body parts were hit that frame.
  • The HUD itself has been updated with better graphics based on this OpenClipArt design. Here are some screenshots of the new assets:

1.jpg.c759dc0c358922e832bf18e97d6377f2.jpg2.jpg.d70184199aa0204d36f3c85460f15956.jpg3.jpg.ace800d2bcb86fa4d5c681ff164da219.jpg4.jpg.c250aa807f9fe62016b9788748d1df13.jpg

No plans for the skill component yet, mainly due to scripting system limitations leaving a lot up in the air. But finishing the damage module helps pave the way.

player_damage_1.1.pk4

Edited by MirceaKitsune
  • Like 1
Link to post
Share on other sites

Attack speed is more or less depending on the animation speed. I am not sure to which degree this is adjustable mid game as I haven't worked much with animations yet but it is possible to replace animations with other ones via script. So you could have several attack animations of the same type differing in speed.

 

You wrote that the player cannot live without his head or torse, but I fairly doubt it will increase its lifespan if his arm gets chopped off. ;)

 

Movement can be restricted via setMoveHinderance(...) (to make him slower) or via Immobilizations (to disallow jumping or climbing).

 

In regards to the air lowering I suggest taking a look at the breath potion script. I think it utilizes the heal function which can also get negative arguments to lower health or, in this case, air amount.

 

If I may share my opinion I would like to state that I was no big fan of the health system in the first Deus Ex. Having the overall health amount spread over several limbs makes it hard to really judge on how harmed you are and the effects caused by wounded body parts is either neglectable (low damage) or becomes such an annoyance, that players will probably tend to reload their last safe (high damage). And if they don't wanna do the latter as too much progress would get lost, they may have to live with the consequences for a while (annoying and after a while even frustrating) or will immediately heal the wounds, which leads back to the case were the effects are neclectable. In return I don't really see any benefit caused by this system, especially in a game were the player avoids confrontation and fights anyways. Just my two cents, though.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to post
Share on other sites

Feedback wanted, please share your thoughts and suggestions:

I'm still thinking on how I'm going to implement skill / augmentation upgrading. I've already started implementing the boosts themselves and they're proving fairly easy to do, however the system for the player to choose what to upgrade is going to be tricky. So far I had several ideas coming to mind but none seems to really fit.

I initially thought about using loot as skill points, making it so that pressing the use key with the loot inventory icon selected would open a menu or upgrade a slot based on the direction the player's moving in. I could override the grid inventory GUI entity, but do I really want to do that especially in the initial stage? There must be a better way.

I decided I'd like to go for an inventory item per augmentation, like the lantern or the health potion and such: Something the player picks up from the world, selects in the items menu, then uses to upgrade. There should be no functionality issue with this approach... except for one big problem: How on Earth am I going to decide where those items spawn?! This mod isn't intended to ever become default and there's no way the creator of every FM is going to update maps and place my items throughout the world. Thus I need an automated way to make them appear in random locations... one that's logical and makes sense, doesn't ruin the gameplay, and is compatible with all FM's.

The first approach I thought about is looping through common entities and giving each type of upgrade a random chance of appearing on various objects. For example some upgrade orbs could be found in the flames of a fireplace, other booster chips stuck in paintings or large loot items. This method is unpredictable and feels very questionable.

So I thought of yet another way: What if I make them possible to loot off AI? They could be found as implants in the backs of AI's heads, different augmentations specific to different person types (some for guards, others for builders, some for nobles, etc). It shouldn't be an issue to loop through all AI on the map then probabilistically spawn an item and bind it to the right joint! But I'm not sure if this might ruin characters in some FM's, it too is a questionable method.

The simplest way would appear to be making them randomly spawn in some chests or drawers. It could be done: Every compartment contains an atdm:target_set_frobable so I could easily loop through all those entities on the map and choose to spawn an item in the middle of one. In this case it would look logical, however I'd have no control over where they appear. For example why would an upgrade chip be in a poor taffer's chest, considering such a thing would be considered very expensive?

I'm at a loss and could really use other people's ideas. What pattern and script functions would you recommend I use for automatically spawning small inventory items, making them appear in logical places on maps where they haven't been placed manually? There must be a good pattern and I feel I'm getting close, but I still can't find a way that truly feels right.

Link to post
Share on other sites

Just so I understand you correctly. You aim to create a mod that will automatically turn current (and future) FM's into cyberpunk missions?! And you expect everything to work out in regards to gameplay?! That's probably not going to work.

 

I always assumed that you either want to create your own missions or provide a fundament for others to create missions on. In that case the placement of items would obviously be up to the mapper and your problem is solved.

  • Like 1

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to post
Share on other sites
6 hours ago, Obsttorte said:

Just so I understand you correctly. You aim to create a mod that will automatically turn current (and future) FM's into cyberpunk missions?! And you expect everything to work out in regards to gameplay?! That's probably not going to work.

 

I always assumed that you either want to create your own missions or provide a fundament for others to create missions on. In that case the placement of items would obviously be up to the mapper and your problem is solved.

Not quite, that is a different project. The system I'm designing in this thread (per-limb damage with player skills / augmentations) is intended as an addon for vanilla TDM as well, although my real cyberpunk mod (The Dark Module) will contain it by default since I want that there. I intend on making it look and work well under both. With maps designed for TDMU this won't be a problem as I'll be adding those upgrades in-world: The issue is with it working on vanilla TDM maps too.

The end goal of this plugin is to add a bit of extra realism and a touch of complexity to the gameplay. It's no secret the system is inspired by the first DeusEx, which remains far among my favorite games of all times, being an inspiration for what I want to make my TDMU mod like too. I'm NOT trying to copy everything from it to the letter though, as DX had design choices I wasn't a big fan of myself, in its case going the other extreme where it had so many features and you rarely used them all. I do however wish to take some basic mechanisms I really enjoyed and transpose them to TDM in a fitting way.

Limb damage always seemed like a fun and original idea through its power and simplicity: The player needs to be aware of where they take damage from... if not they aren't going to simply see a bar decrease and not worry about it till it reaches 0, but experience real effects (like accuracy decreasing) which make advancing slightly harder. Note that in this addon unlike DX, the original damage system is left untouched, meaning you still lose and gain normal health the exact same way: Limb health is completely separate and only adds or removes certain status effects based on how much you were damaged / healed... normal health should be considered your internal damage, limbs are external damage to the body.

The skill system plans to follow DX's augmentations, but once more with important differences: They're always on, you don't toggle them with an F## key which I always found annoying to keep track of. I don't know if I'm going to implement power and have them turn off once the player is drained, at least for starters I'll make them without a power system... if they will it's only going to be while they're used though, for instance the speed enhancement only drains power while the player is walking. Also I plan on having just 8 rather than 12, without any choices between upgrades (you can get all available ones installed and maxed out at the same time). There will be an item for each aug used to install and upgrade it... using it first will install that aug at level 1, then you can upgrade it up to level 4. I'm still deciding on exactly which augmentations I'm going to have based on what the scripting system makes easiest... several were successfully implemented last night and I should have a final list of boosts soon.

Edited by MirceaKitsune
Link to post
Share on other sites

Speaking of porting fun features from DX, say hello to the crosshair which indicates the alliance of targets you're looking at! The crosshair only appears once you choose to install its augmentation: It will be white by default, green when looking at a friend, yellow when looking at a neutral, red when looking at an enemy. Colors may be filtered out by the upgrade level of the augmentation, meaning certain states can only be seen at some upgrade stages.

1.jpg.04f2e16373f7f0fdb91e0eff4f2b997f.jpg2.jpg.aaeabdcb152c91a1729406a772104043.jpg3.jpg.493b842e2c53f26a5e46e2895d0d2557.jpg4.jpg.9b2caf0efcad5edda18d4ccd0693c908.jpg

  • Like 1
Link to post
Share on other sites

Happy to announce that in one day (albeit spent working exclusively on this) I managed to implement the effects for all augmentations I plan to have available in the module. The fun part is that I managed to get so many good ideas working with the script system after all, I went ahead and implemented 12 augs rather than just 8: I don't know if in the future I'm going to combine a few of them, such as speed enhancement with jump enhancement or crosshair with info text, which would make sense but I'll have to think upon it. For now every effect is independent and plays a role that's definitely going to be noticeable during everyday gameplay :D Each augmentation has a maximum of 4 levels, the item will first install it at level 1 then you can upgrade 3 more times as you find more of that chip item. The (so far) final enhancements include:

  1. Crosshair: At level 1 the player gains a white crosshair which doesn't do anything yet. At level 2 the crosshair will highlight green over allies. At level 3 it learns to become yellow over neutrals. At level 4 it finally includes the red color over enemies.
  2. Info: A small field of text appears under the crosshair when the player is standing near enemies, containing information on the enemies around them. At level 1 the number of enemies close to you is printed. At level 2 the alert level of the most alerted AI on the list is shown. At level 3 the movement speed of the fastest moving AI is also printed. At level 4 the health of the most healthy AI is finally added to the list.
  3. Sonar: The player will hear directional clicking noises when there's loot nearby. Higher levels increase the distance at which the player's brain will ping for loot.
  4. Infrared: A light source is permanently added to the player, making it easier to see inside dark rooms. Similar to using the lantern, except the light is very dim and most importantly doesn't increase the lightgem so there's no effect on AI visibility. Higher levels slightly increase the intensity of the light.
  5. Stealth: Reduces the player's lightgem making them harder to detect. Minor visibility decrease at level 1, the player is nearly undetectable even in light at level 4.
  6. Soothing: When an alert enemy AI is standing next to the player, the player transmits waves that cause them to calm down and cease searching sooner. Only works if the AI is still searching, has no effect once the AI is alert enough to attack. Higher levels increase the range as well as the amount down to which the alert can be lowered: At level 1 an AI sitting close to the player will calm down slightly faster, whereas at level 4 the AI will almost instantly forget they were searching once getting close enough.
  7. Speed: Walk speed is increased, only active while running with no effect when sneaking. Level 1 slightly boosts movement while level 4 lets the player run like the wind.
  8. Jumping: Jump height is increased, the higher the level the further up you can go.
  9. Glide: The player falls at a slower speed. Some fall damage may still be taken at level 1, at level 4 the player will descend at a safe and pace from any height.
  10. Heal: The player slowly regains health over time, faster the higher the level of the aug. Health is only regenerated when the player is standing still.
  11. Knockout: When an enemy is alert to the point of attacking, the player blasts them with psy waves that cause them to be knocked out and fall where they stand, protecting the player from human attackers who are chasing them or their allies. Higher values slightly increase the range.
  12. Extinguish: Fire based light sources will automatically get extinguished when near the player, walking next to a candle or torch will cause it to automatically go off. Note that relighting candles (such as using the match) will no longer be possible. Higher aug levels increase the range at which flames are snuffed out.

And that's it. I might add 4 more empty slots which could perhaps be used by other mods to add their own augs, but as far as the defaults go I'm really happy with this set. They were only tested on my simple test map but I love the functionalities and each one looks like it's going to have an impact on gameplay and be useful! Needless to say they will greatly change how TDM plays, but that's pretty obvious and the addon is intended for players who want that.

Share your thoughts please: Which one is your favorite augmentation and which would you rather see replaced? And if you think one doesn't belong, could you suggest something else in its place? It would be nice to hear feedback now when this is in early stages... later down the road it's unlikely I will make massive changes again.

The skills aren't ready to be included in an update yet, there's no way to actually set them. I'll just post them here instead, the new script is only 300 lines long.

  • player_skills.script
    Spoiler

     

    
    #define SKILLS_MAXLEVEL 4
    #define SKILLS_MAXTRACE 1000
    #define SKILLS_ALERTLEVEL_ATTACK 24
    
    float skills_gui;
    entity skills_light;
    float skills_crosshair;
    float skills_timer_sonar, skills_timer_heal;
    
    boolean skills_fac_standing, skills_fac_swimming;
    
    void player_skills_fac_set()
    {
    	player ent = $player1;
    	skills_fac_standing = ent.AI_ONGROUND && !ent.isInLiquid();
    	skills_fac_swimming = ent.isInLiquid();
    }
    
    void player_skills_update_crosshair()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_crosshair");
    
    	// Get the AI currently being traced
    	entity trace_ent;
    	if(lv > 0)
    	{
    		vector ang = ent.getViewAngles();
    		vector ang_fwd = sys.angToForward(ang);
    		vector pos_start = ent.getEyePos();
    		vector pos_end = pos_start + ang_fwd * SKILLS_MAXTRACE;
    		sys.tracePoint(pos_start, pos_end, CONTENTS_BODY, ent);
    		trace_ent = sys.getTraceEntity();
    	}
    
    	// 0 = Off, 1 = On, 2 = Friendly AI, 3 = Neutral AI, 4 = Enemy AI
    	float crosshair = 0;
    	if(lv >= 4 && trace_ent.isEnemy(ent))
    		crosshair = 4;
    	else if(lv >= 3 && trace_ent.isNeutral(ent))
    		crosshair = 3;
    	else if(lv >= 2 && trace_ent.isFriend(ent))
    		crosshair = 2;
    	else if(lv >= 1)
    		crosshair = 1;
    
    	// Fade from the old crosshair color and apply the new one
    	if(crosshair == 0 && skills_crosshair != 0)
    	{
    		ent.callGui(skills_gui, "Crosshair_OnToOff");
    		skills_crosshair = 0;
    	}
    	else if(crosshair == 1 && skills_crosshair == 2)
    	{
    		ent.callGui(skills_gui, "Crosshair_FriendToOn");
    		skills_crosshair = 1;
    	}
    	else if(crosshair == 1 && skills_crosshair == 3)
    	{
    		ent.callGui(skills_gui, "Crosshair_NeutralToOn");
    		skills_crosshair = 1;
    	}
    	else if(crosshair == 1 && skills_crosshair == 4)
    	{
    		ent.callGui(skills_gui, "Crosshair_EnemyToOn");
    		skills_crosshair = 1;
    	}
    	else if(crosshair == 1 && skills_crosshair != 1)
    	{
    		ent.callGui(skills_gui, "Crosshair_OffToOn");
    		skills_crosshair = 1;
    	}
    	else if(crosshair == 2 && skills_crosshair != 2)
    	{
    		ent.callGui(skills_gui, "Crosshair_OnToFriend");
    		skills_crosshair = 2;
    	}
    	else if(crosshair == 3 && skills_crosshair != 3)
    	{
    		ent.callGui(skills_gui, "Crosshair_OnToNeutral");
    		skills_crosshair = 3;
    	}
    	else if(crosshair == 4 && skills_crosshair != 4)
    	{
    		ent.callGui(skills_gui, "Crosshair_OnToEnemy");
    		skills_crosshair = 4;
    	}
    }
    
    void player_skills_update_info()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_info");
    	float mod = 100 * lv;
    	string info = "";
    
    	// Record info on nearby enemies, max values are always shown
    	float info_count = 0;
    	float info_warn = 0;
    	float info_move = 0;
    	float info_health = 0;
    	entity ent_find;
    	do
    	{
    		ent_find = sys.getNextEntity("AIUse", "AIUSE_PERSON", ent_find);
    		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
    		{
    			ai ai_find = ent_find;
    			if(ai_find.isEnemy(ent) && !ai_find.AI_DEAD && !ai_find.AI_KNOCKEDOUT)
    			{
    				vector vel = ai_find.getLinearVelocity();
    				float this_move = (vel_x + vel_y + vel_z) / 3;
    				float this_warn = bound(ai_find.AI_AlertLevel / SKILLS_ALERTLEVEL_ATTACK, 0, 1);
    				float this_health = ai_find.getHealth();
    				if(this_move > info_move)
    					info_move = this_move;
    				if(this_warn > info_warn)
    					info_warn = this_warn;
    				if(this_health > info_health)
    					info_health = this_health;
    				info_count += 1;
    			}
    		}
    	}
    	while(ent_find != $null_entity);
    
    	if(info_count > 0 && lv >= 1)
    		info = info + info_count + " enemies nearby\n";
    	if(info_warn > 0 && lv >= 2)
    		info = info + "Alert: " + sys.strMid(info_warn, 0, 4) + "\n";
    	if(info_move > 0 && lv >= 3)
    		info = info + "Movement: " + sys.strMid(info_move, 0, 4) + "\n";
    	if(info_health > 0 && lv >= 4)
    		info = info + "Health: " + sys.strMid(info_health, 0, 4) + "\n";
    
    	ent.setGuiString(skills_gui, "HUD_PlayerSkills_Info", info);
    }
    
    void player_skills_update_sonar()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_sonar");
    	float mod = 100 * lv;
    
    	if(sys.getTime() > skills_timer_sonar)
    	{
    		entity ent_find = find_closest("inv_category", "Loot", mod);
    		if(ent_find != $null_entity)
    			ent_find.startSoundShader("typing", SND_CHANNEL_ANY);
    		skills_timer_sonar = sys.getTime() + 1;
    	}
    }
    
    void player_skills_update_infrared()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_infrared");
    	float mod = 0.025 * lv;
    
    	if(lv == 0)
    	{
    		if(skills_light != $null_entity)
    		{
    			skills_light.remove();
    			skills_light = $null_entity;
    		}
    	}
    	else
    	{
    		if(skills_light == $null_entity)
    		{
    			skills_light = sys.spawn("player_skills_infrared");
    			skills_light.setOrigin(ent.getEyePos());
    			skills_light.bind(ent);
    		}
    
    		skills_light.setLightParms(mod, mod, mod, 1);
    	}
    }
    
    void player_skills_update_stealth()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_stealth");
    	float mod = 4 * lv;
    
    	ent.setLightgemModifier("skills", -mod);
    }
    
    void player_skills_update_soothing()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_soothing");
    	float mod = 50 * lv;
    	float mod_alert = (SKILLS_ALERTLEVEL_ATTACK / (1 + SKILLS_MAXLEVEL)) * (1 + SKILLS_MAXLEVEL - lv);
    	if(lv == 0)
    		return;
    
    	entity ent_find;
    	do
    	{
    		ent_find = sys.getNextEntity("AIUse", "AIUSE_PERSON", ent_find);
    		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
    		{
    			ai ai_find = ent_find;
    			if(ai_find.isEnemy(ent) && !ai_find.AI_DEAD && !ai_find.AI_KNOCKEDOUT && ai_find.AI_AlertLevel < SKILLS_ALERTLEVEL_ATTACK)
    				if(ai_find.AI_AlertLevel > mod_alert)
    					ai_find.setAlertLevel(mod_alert);
    		}
    	}
    	while(ent_find != $null_entity);
    }
    
    void player_skills_update_speed()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_speed");
    	float mod = 5 * lv;
    	if(lv == 0 || !ent.AI_RUN)
    		return;
    
    	vector vel = ent.getMove();
    	vector ang = ent.getViewAngles();
    	vector boost = '0 0 0';
    	if(skills_fac_standing || skills_fac_swimming)
    		boost_x = (vel_x * sys.cos(ang_y)) + (vel_y * sys.cos(ang_y - 90));
    	if(skills_fac_standing || skills_fac_swimming)
    		boost_y = (vel_x * sys.sin(ang_y)) + (vel_y * sys.sin(ang_y - 90));
    	if(skills_fac_swimming)
    		boost_z = vel_z;
    	boost *= mod;
    
    	ent.applyImpulse(ent, 0, ent.getOrigin(), boost);
    }
    
    void player_skills_update_jump()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_jump");
    	float mod = 25 * lv;
    	if(lv == 0)
    		return;
    
    	vector vel = ent.getMove();
    	vector boost = '0 0 0';
    	if(vel_z > 0 && skills_fac_standing)
    		boost_z = vel_z;
    	boost *= mod;
    
    	ent.applyImpulse(ent, 0, ent.getOrigin(), boost);
    }
    
    void player_skills_update_glide()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_glide");
    	float mod = 100 * (1 + SKILLS_MAXLEVEL - lv);
    	if(lv == 0)
    		return;
    
    	vector vel = ent.getLinearVelocity();
    	if(vel_z < -mod)
    	{
    		vel_z = -mod;
    		ent.setLinearVelocity(vel);
    	}
    }
    
    void player_skills_update_heal()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_heal");
    	float mod = 1 * lv;
    	if(lv == 0)
    		return;
    
    	if(sys.getTime() > skills_timer_heal)
    	{
    		vector vel = ent.getMove();
    		if(vel_x == 0 && vel_y == 0 && vel_z == 0)
    		{
    			float hl = ent.getHealth();
    			hl = bound(hl + mod, 0, 100);
    			ent.setHealth(hl);
    		}
    		skills_timer_heal = sys.getTime() + 1;
    	}
    }
    
    void player_skills_update_knockout()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_knockout");
    	float mod = 50 * lv;
    	if(lv == 0)
    		return;
    
    	entity ent_find;
    	do
    	{
    		ent_find = sys.getNextEntity("AIUse", "AIUSE_PERSON", ent_find);
    		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
    		{
    			ai ai_find = ent_find;
    			if(ai_find.isEnemy(ent) && !ai_find.AI_DEAD && !ai_find.AI_KNOCKEDOUT && ai_find.AI_AlertLevel >= SKILLS_ALERTLEVEL_ATTACK)
    				ai_find.KO_Knockout(ent);
    		}
    	}
    	while(ent_find != $null_entity);
    }
    
    void player_skills_update_extinguish()
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey("augmentations_extinguish");
    	float mod = 50 * lv;
    	if(lv == 0)
    		return;
    
    	entity ent_find;
    	do
    	{
    		ent_find = sys.getNextEntity("AIUse", "AIUSE_LIGHTSOURCE", ent_find);
    		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
    			ent_find.extinguishLights();
    	}
    	while(ent_find != $null_entity);
    }
    
    void player_skills_upgrade(string aug)
    {
    	player ent = $player1;
    	float lv = ent.getFloatKey(aug);
    	lv = bound(lv + 1, 0, SKILLS_MAXLEVEL);
    	ent.setKey(aug, lv);
    }
    
    void player_skills()
    {
    	sys.waitFrame();
    	player ent = $player1;
    	skills_gui = ent.createOverlay("guis/player_skills.gui", 1);
    
    	// Init the level keys
    	ent.setKey("augmentations_crosshair", 0);
    	ent.setKey("augmentations_info", 0);
    	ent.setKey("augmentations_sonar", 0);
    	ent.setKey("augmentations_infrared", 0);
    	ent.setKey("augmentations_stealth", 0);
    	ent.setKey("augmentations_soothing", 0);
    	ent.setKey("augmentations_speed", 0);
    	ent.setKey("augmentations_jump", 0);
    	ent.setKey("augmentations_glide", 0);
    	ent.setKey("augmentations_heal", 0);
    	ent.setKey("augmentations_knockout", 0);
    	ent.setKey("augmentations_extinguish", 0);
    
    	// Upgrade all augs to max level for testing purposes, this will be done by using upgrade items
    	float i;
    	for(i = 1; i <= 4; i++)
    	{
    		player_skills_upgrade("augmentations_crosshair");
    		player_skills_upgrade("augmentations_info");
    		player_skills_upgrade("augmentations_sonar");
    		player_skills_upgrade("augmentations_infrared");
    		player_skills_upgrade("augmentations_stealth");
    		player_skills_upgrade("augmentations_soothing");
    		player_skills_upgrade("augmentations_speed");
    		player_skills_upgrade("augmentations_jump");
    		player_skills_upgrade("augmentations_glide");
    		player_skills_upgrade("augmentations_heal");
    		player_skills_upgrade("augmentations_knockout");
    		player_skills_upgrade("augmentations_extinguish");
    	}
    
    	while(1)
    	{
    		sys.waitFrame();
    
    		// Call the update function of each effect
    		player_skills_update_crosshair();
    		player_skills_update_info();
    		player_skills_update_sonar();
    		player_skills_update_infrared();
    		player_skills_update_stealth();
    		player_skills_update_soothing();
    		player_skills_update_speed();
    		player_skills_update_jump();
    		player_skills_update_glide();
    		player_skills_update_heal();
    		player_skills_update_knockout();
    		player_skills_update_extinguish();
    
    		// Update skill factors so current values can be used in the next call
    		player_skills_fac_set();
    	}
    }

     

     

     

     

  • player_skills.def:
    Spoiler

     

    
    entitydef player_skills_infrared
    {
    	"inherit" "atdm:light_base"
    
    	"editor_usage"  "Infrared light for player."
    
    	"mins" "-1 -1 -1"
    	"maxs" "1 1 1"
    
    	// Don't clip the player
    	"clipmodel_contents" "32768"
    
    	// Light properties
    	"falloff" "1"
    	"texture" "lights/tdm_lanternlight"
    	"_color" "1 1 1" // Updated by setLightParms
    	"light_radius" "256 256 256"
    
    	"noshadows" "1"
    	"ai_see" "0"
    }

     

     

     

     

  • player_skills.gui:
    Spoiler
    
    #define HUD_OPACITY "gui::HUD_Opacity" 
    #define INFO "gui::HUD_PlayerSkills_Info" 
    
    windowDef Desktop
    {
    	rect 0, 0, 640, 480
    	visible 1
    	nocursor 1
    	matcolor 1, 1, 1, 1
    	background ""
    
    	windowDef Crosshair
    	{
    		rect 316, 234, 8, 12
    		matcolor 1, 1, 1, 0
    		background "guis/assets/crosshair"
    		visible 1
    	}
    
    	windowDef Info
    	{
    		rect 256, 248, 128, 64
    		matcolor 1, 1, 1, 1
    		background ""
    		font "fonts/stone"
    		textscale 0.125
    		textalign 1
    		text INFO
    		visible 1
    	}
    
    	onNamedEvent Crosshair_OffToOn
    	{
    		transition "Crosshair::matcolor" "1 1 1 0" "1 1 1 1" "250";
    	}
    
    	onNamedEvent Crosshair_OnToOff
    	{
    		transition "Crosshair::matcolor" "1 1 1 1" "1 1 1 0" "250";
    	}
    
    	onNamedEvent Crosshair_OnToFriend
    	{
    		transition "Crosshair::matcolor" "1 1 1 1" "0 1 0 1" "250";
    	}
    
    	onNamedEvent Crosshair_OnToNeutral
    	{
    		transition "Crosshair::matcolor" "1 1 1 1" "1 1 0 1" "250";
    	}
    
    	onNamedEvent Crosshair_OnToEnemy
    	{
    		transition "Crosshair::matcolor" "1 1 1 1" "1 0 0 1" "250";
    	}
    
    	onNamedEvent Crosshair_FriendToOn
    	{
    		transition "Crosshair::matcolor" "0 1 0 1" "1 1 1 1" "250";
    	}
    
    	onNamedEvent Crosshair_NeutralToOn
    	{
    		transition "Crosshair::matcolor" "1 1 0 1" "1 1 1 1" "250";
    	}
    
    	onNamedEvent Crosshair_EnemyToOn
    	{
    		transition "Crosshair::matcolor" "1 0 0 1" "1 1 1 1" "250";
    	}
    }

     

     

Edited by MirceaKitsune
  • Like 1
Link to post
Share on other sites

That's pretty damn interesting, good to see such effects implemented and a cool first use of the new addon system. Definitely a case for some balancing i.e.:

- stealth: this would really work better as a subtle effect even at level 4, not "nearly undetectable even in light"

- I think "crosshair" and "info" would work better if merged, so each upgrade has more meat on it, especially since "info" requires a crosshair. Maybe the freed spot could be taken up by "silent steps".

- knockout might be better as non-guaranteed KO with max one chance per AI. Extinguish could also be chance-based for big light sources like torches.

- sonar with a max search distance of 400 units is probably going to be active a lot of the time, might want to go somewhat shorter.

- maybe add an inventory item to the player that can be used to toggle at least some of these effects, in case they interfere with what the player wants to do.

For your question: check the Campaigns wiki article's Scripting subsection for persistent variables.

Also, what's your method for find_closest()?

 

I think the single main issue with this mod so far is the theme. It'd probably be easier for a player to immersive himself if he were unlocking "supernatural powers" like "extrasensory perception" by finding "runes/orbs/glyphs" rather than "augmenting" abilities such as "sonar" with "implants/microchips". Then it could be considered as a Dishonored/Thief 4 inspired mod rather than Deus Ex inspired. For your cyberpunk mod that's a different matter of course.

Link to post
Share on other sites
Quote

They were only tested on my simple test map but I love the functionalities and each one looks like it's going to have an impact on gameplay

 

You may want to playtest them in actual maps.  Some of those features (slowfall and speed in particular) have already been experimented with, and were found to cause unforseen consequences during play.  If you've managed to solve those problems then those potions could finally be added.

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

That's pretty damn interesting, good to see such effects implemented and a cool first use of the new addon system. Definitely a case for some balancing i.e.:

- stealth: this would really work better as a subtle effect even at level 4, not "nearly undetectable even in light"

- I think "crosshair" and "info" would work better if merged, so each upgrade has more meat on it, especially since "info" requires a crosshair. Maybe the freed spot could be taken up by "silent steps".

- knockout might be better as non-guaranteed KO with max one chance per AI. Extinguish could also be chance-based for big light sources like torches.

- sonar with a max search distance of 400 units is probably going to be active a lot of the time, might want to go somewhat shorter.

- maybe add an inventory item to the player that can be used to toggle at least some of these effects, in case they interfere with what the player wants to do.

For your question: check the Campaigns wiki article's Scripting subsection for persistent variables.

Also, what's your method for find_closest()?

 

I think the single main issue with this mod so far is the theme. It'd probably be easier for a player to immersive himself if he were unlocking "supernatural powers" like "extrasensory perception" by finding "runes/orbs/glyphs" rather than "augmenting" abilities such as "sonar" with "implants/microchips". Then it could be considered as a Dishonored/Thief 4 inspired mod rather than Deus Ex inspired. For your cyberpunk mod that's a different matter of course.

Very good points, I agree with most of them and am thinking of making some changes in this direction. A couple of problems though:

  • Stealth: Currently I'm using (4 * lv) meaning that at level 4 you get a 16 lightgem deduction. I agree that's a bit too much so I've lowered it to (2 * lv) meaning max 8: I tested that possibility too and it's just enough to make the effect worth it but without making the player too hidden.
  • Silence: The plan was to create a separate augmentation for making the player silent while walking. The problem here is the scripting system: I could not find a way to disable footstep sounds, or for my use case decrease their volume with level (both the audio and what the AI hears). If there is a function for this please let me know!
  • Crosshair & Info: The original plan was to merge these two. I prefer displaying info on the targeted AI anyway rather than surrounding enemies, the two systems would indeed sit better as one aug. Now that someone else confirmed this I think I'll do the right thing and merge them together in the next update: In its place I could put the silence aug, but again I need an entity call on the player to lower footstep sounds.
  • Knockout: This one is a bit tricky since you can't easily scale the effect, either you knock the AI out or you don't. As this is a powerful upgrade I want to limit it as much as possible as to not overpower the player. One thing I think I'll do is fixing the distance at 100 (just near the player) rather than increasing it with level like other effects. Then I believe I'll use a once-per-second probability: 0.25 at level 1, 0.5 at level 2, 0.75 at level 3, 1 at level 4.
  • Sonar: I've already ensured that only one ping (per second) for the closest item is heard, not all items in range. That would have probably caused an audio mess.
  • Extinguish: This is slightly overpowered I agree. One idea was to only allow larger light sources to be put out when the aug is upgraded. The problem once more is implementation: How do I know the size of each light source? I find lights by scanning for AIUse == AIUSE_LIGHTSOURCE which doesn't give me any information other than "it's a light"; I'd probably need to find a key (via light.getKey()) which always represents the size of the flame for at least the default light entities.

Yes, I forgot to share the find_closest function in the common utility script... here it is:

Spoiler



// Returns the entity closest to the player within a given distance
entity find_closest(string key, string val, float dist)
{
	player ent = $player1;
	entity ent_find = $null_entity;
	entity ent_closest = $null_entity;
	float dist_closest = dist;

	do
	{
		ent_find = sys.getNextEntity(key, val, ent_find);
		if(ent_find != $null_entity)
		{
			float dist_to = ent.distanceTo(ent_find);
			if(dist_to < dist_closest)
			{
				ent_closest = ent_find;
				dist_closest = dist_to;
			}
		}
	}
	while(ent_find != $null_entity);
	return ent_closest;
}


 

Story wise I once again want to keep it compatible with both TDM and my upcoming cyberpunk conversion. I'll do this by keeping the explanation between augmentations neutral; In TDM they can be considered either a form of magic, either a creation of the Inventors Guild albeit they're a bit above 1800's level of technology. In my mod where the action will typically take place after the 2040's they'll fit right in at an age where most people will be augmented.

54 minutes ago, Springheel said:

 

You may want to playtest them in actual maps.  Some of those features (slowfall and speed in particular) have already been experimented with, and were found to cause unforseen consequences during play.  If you've managed to solve those problems then those potions could finally be added.

Once they're in working condition I plan to do just that, by installing the mod globally and using it during normal gameplay. I want the code to be shiny first, still a bit to go.

I've looked at the speed potion and slow fall potion codes, that's actually where I took some inspiration from. That is their forum threads, they haven't been added to vanilla TDM yet (only placeholder entities are in). My speed boost works pretty much the same way as that speed potion. The slow fall I implemented in a slightly different way as it worked better for me: I check the player's linear velocity and if vel_z gets under say -100 I simply set it back to -100 to keep it there. Both the speed boost, jump boost, and gliding seem to work well: The only annoyance I saw a few times is that the effect can be fuzzy and you may see the view shaking between frames... I make sure the system runs each frame without delay to prevent this effect too. No clipping or taking damage from hitting walls and such otherwise, but testing on real maps will be important.

Link to post
Share on other sites

Crosshair and Information augmentations were merged into one as follows. Level 1: The crosshair appears, this time all colors work right away. Level 2: The name / class of the traced AI is printed below the crosshair. Level 3: The alert level of that AI is also printed below it. Level 4: The health of the AI is also printed below that.

I'm thinking of merging speed enhancement and jump as well, given they do roughly the same thing and use the same script pieces to do it. However this would leave me down by yet another augmentation slot, and I'm panning to stick to 12 defaults now that I went this route. What would you suggest putting in its place, which is within the realm of the script system to support? Share some creative ideas that I haven't thought of and I'll take a look when I get back!

Knockout aug was also fixed to be less powerful. The range is now set at a low 50 units, meaning an AI cannot be knocked out from a distance but only if the player is basically touching them. The effect now occurs on a timer, it's this timer that is affected by the level as follows: Level 1 = 6 seconds, Level 2 = 4 seconds, Level 3 = 2 seconds, Level 4 = 0 seconds (instant at max level). The player can essentially knock out a violent AI by bumping into them, but if the aug isn't upgraded they may have to bump them for a few seconds to succeed, increasing the risk of the player getting hurt before it works: I gave this new functionality a few tests and it feels very balanced to me.

Oh... and I plan on doing a little mixup which will give the limb damage more use: Each augmentation will be tied to a certain limb which it's implemented in. If that limb reaches 0 health, all augmentations associated with it stop working until it gets restored. In exchange I'm thinking of removing all death penalties from limbs, so losing your torso or head will no longer kill you... after a conversation last night I thought more on it and agree that normal health reaching 0 is the only thing that should kill the player while limbs are just for penalties.

Link to post
Share on other sites

Took some new decisions while I was away, I think you're going to like these as well :) First of all how augmentations will relate to limbs: Each aug will be specific to a body part... like healing to the torso, infrared to the head. As long as that limb stays above 0 health, the performance of the aug will not be affected and you can keep using it. However if you allow a limb to reach 0 health and disappear from the HUD, all augs installed on it will be permanently lost! Once your limb is healed again the player will need to find new items to install and start upgrading lost augmentations from scratch. This offers a way to make the system cyclical so that there's a way to reset, while further encouraging the player to pay attention to their limb damage on top of the little penalties already associated with it.

I decided on a new augmentation to replace the one lost in the merger: The player gains the ability to unlock locked doors by merely looking at them, granted they can be lockpicked of course. The level of the augmentation determines the amount of pins a door can have before frob-highlighting automatically unlocks it: Level 1 will only unlock simple doors with 1 pin, level 4 can unlock doors with up to 4 pins. I should have the script functions needed: I can get the door with $player1.getFrobbed then unlock it with door.Unlock() which from what I'm reading should do the trick... the string length of door.getKey("lock_picktype") (eg: "sts" == 3) will tell me how many pins the door has.

The knockout aug will be turned into a self-defense system: I'm going to make it activate when the player is damaged. The level changes the damage threshold under which the AI gets blasted, the effect will always be immediate. This means that when a guard hits you with his sword, he's going to drop to the ground the moment he dealt damage you.

Link to post
Share on other sites

I think useful info about an AI would be how skilled it is at melee combat. That could take the place of the entity name & classname, which don't really have anything to do with gameplay.

For extinguishing only large light sources, you'll probably have to open DR and compare some torch flames/campfires with candle/oil flames and see which spawnargs are unique to either.

For silent steps, there are script events to modify acuity of AIs. Could probably multiply the auditory acuity of all AIs on the map as the player becomes more quiet or interferes with their senses.

Link to post
Share on other sites

What would be the best function or spawnarg to get the melee skill of an AI? Perhaps something that could work for archer skills as well so it's not just for melee? I agree the name is pretty much a placeholder, there's little to not use for it just something to fill each upgrade level with.

Changing the acuity of all AI sounds a bit too hacky, as this would also offset their hearing toward other AI or world events. Plus the player hears their own footsteps at the same volume so it doesn't make sense. For now it seems best to skip this one... maybe I can find something better in its place?

For tonight I finished implementing the ideas in my last post: Crosshair and HUD info are now one. A door unlocking aug was added... it only works for normal doors and not chests as there are some weird issues with the froblock entity, but this is better as it leaves some doors up to the standard lockpicks. Knockout aug works as a self defense mechanism.

I began preliminary work on updating the HUD to include the new augs, this should be ready tomorrow then the upgrade items are the last part. Note that the bottom row will be unused, this is mostly a mockup using one test overlay which I'll next separate into individual pieces.

Spoiler

// https://modwiki.dhewm3.org/GetButtons_%28script_event%29

#define SKILLS_MAXLEVEL 4
#define SKILLS_MAXTRACE 1000
#define SKILLS_ALERTLEVEL_ATTACK 24

float skills_gui;
entity skills_light;
float skills_crosshair;
float skills_timer_sonar, skills_timer_heal;

boolean skills_fac_standing, skills_fac_swimming, skills_fac_frobbing;
float skills_fac_health;

void player_skills_fac_set()
{
	player ent = $player1;
	skills_fac_health = ent.getHealth();
	skills_fac_standing = ent.AI_ONGROUND && !ent.isInLiquid();
	skills_fac_swimming = ent.isInLiquid();
	skills_fac_frobbing = ent.getFrobbed() != $null_entity;
}

void player_skills_update_crosshair()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_crosshair");
	float crosshair = 0;
	string info = "";

	if(lv > 0)
	{
		crosshair = 1;
		vector ang = ent.getViewAngles();
		vector ang_fwd = sys.angToForward(ang);
		vector pos_start = ent.getEyePos();
		vector pos_end = pos_start + ang_fwd * SKILLS_MAXTRACE;
		sys.tracePoint(pos_start, pos_end, CONTENTS_BODY, ent);
		ai ai_trace = sys.getTraceEntity();

		if(ai_trace != $null_entity && !ai_trace.AI_DEAD && !ai_trace.AI_KNOCKEDOUT)
		{
			// 0 = Off, 1 = On, 2 = Friendly AI, 3 = Neutral AI, 4 = Enemy AI
			if(ai_trace.isEnemy(ent))
				crosshair = 4;
			else if(ai_trace.isNeutral(ent))
				crosshair = 3;
			else if(ai_trace.isFriend(ent))
				crosshair = 2;

			// Set crosshair information using data from the traced AI
			if(lv >= 2)
			{
				string this_name = ai_trace.getName();
				info = info + "Name: " + this_name + "\n";
			}
			if(lv >= 3)
			{
				string this_alert = sys.strMid(bound(ai_trace.AI_AlertLevel / SKILLS_ALERTLEVEL_ATTACK, 0, 1), 0, 4);
				info = info + "Alert: " + this_alert + "\n";
			}
			if(lv >= 4)
			{
				string this_health = sys.strMid(ai_trace.getHealth(), 0, 4);
				info = info + "Health: " + this_health + "\n";
			}
		}
	}

	// Fade from the old crosshair color and apply the new one
	if(crosshair == 0 && skills_crosshair != 0)
	{
		ent.callGui(skills_gui, "Crosshair_OnToOff");
		skills_crosshair = 0;
	}
	else if(crosshair == 1 && skills_crosshair == 2)
	{
		ent.callGui(skills_gui, "Crosshair_FriendToOn");
		skills_crosshair = 1;
	}
	else if(crosshair == 1 && skills_crosshair == 3)
	{
		ent.callGui(skills_gui, "Crosshair_NeutralToOn");
		skills_crosshair = 1;
	}
	else if(crosshair == 1 && skills_crosshair == 4)
	{
		ent.callGui(skills_gui, "Crosshair_EnemyToOn");
		skills_crosshair = 1;
	}
	else if(crosshair == 1 && skills_crosshair != 1)
	{
		ent.callGui(skills_gui, "Crosshair_OffToOn");
		skills_crosshair = 1;
	}
	else if(crosshair == 2 && skills_crosshair != 2)
	{
		ent.callGui(skills_gui, "Crosshair_OnToFriend");
		skills_crosshair = 2;
	}
	else if(crosshair == 3 && skills_crosshair != 3)
	{
		ent.callGui(skills_gui, "Crosshair_OnToNeutral");
		skills_crosshair = 3;
	}
	else if(crosshair == 4 && skills_crosshair != 4)
	{
		ent.callGui(skills_gui, "Crosshair_OnToEnemy");
		skills_crosshair = 4;
	}

	// Update the crosshair information text
	ent.setGuiString(skills_gui, "HUD_PlayerSkills_CrosshairInfo", info);
}

void player_skills_update_unlock()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_unlock");
	float mod = lv;
	entity door = ent.getFrobbed();

	// Only unlock when the entity is first frobbed, otherwise locking becomes impossible
	// This also only works for real doors, it won't work on chests
	if(skills_fac_frobbing || door == $null_entity || door.getEntityKey("frob_master") != $null_entity)
		return;

	// Get the number of lock pins using the string length of the spawnarg, eg: "sts" == 3
	string pins_arg = door.getKey("lock_picktype");
	float pins = sys.strLength(pins_arg);

	if(door.IsLocked() && door.IsPickable() && pins <= mod)
		door.Unlock();
}

void player_skills_update_sonar()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_sonar");
	float mod = 100 * lv;

	if(sys.getTime() > skills_timer_sonar)
	{
		entity ent_find = find_closest("inv_category", "Loot", mod);
		if(ent_find != $null_entity)
			ent_find.startSoundShader("typing", SND_CHANNEL_ANY);
		skills_timer_sonar = sys.getTime() + 1;
	}
}

void player_skills_update_infrared()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_infrared");
	float mod = 0.025 * lv;

	if(lv == 0)
	{
		if(skills_light != $null_entity)
		{
			skills_light.remove();
			skills_light = $null_entity;
		}
	}
	else
	{
		if(skills_light == $null_entity)
		{
			skills_light = sys.spawn("player_skills_infrared");
			skills_light.setOrigin(ent.getEyePos());
			skills_light.bind(ent);
		}

		skills_light.setLightParms(mod, mod, mod, 1);
	}
}

void player_skills_update_stealth()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_stealth");
	float mod = 2 * lv;

	ent.setLightgemModifier("skills", -mod);
}

void player_skills_update_soothing()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_soothing");
	float mod = 50 * lv;
	float mod_alert = (SKILLS_ALERTLEVEL_ATTACK / (1 + SKILLS_MAXLEVEL)) * (1 + SKILLS_MAXLEVEL - lv);
	if(lv == 0)
		return;

	entity ent_find;
	do
	{
		ent_find = sys.getNextEntity("AIUse", "AIUSE_PERSON", ent_find);
		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
		{
			ai ai_find = ent_find;
			if(ai_find.isEnemy(ent) && !ai_find.AI_DEAD && !ai_find.AI_KNOCKEDOUT && ai_find.AI_AlertLevel < SKILLS_ALERTLEVEL_ATTACK)
				if(ai_find.AI_AlertLevel > mod_alert)
					ai_find.setAlertLevel(mod_alert);
		}
	}
	while(ent_find != $null_entity);
}

void player_skills_update_speed()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_speed");
	float mod = 5 * lv;
	if(lv == 0 || !ent.AI_RUN)
		return;

	vector vel = ent.getMove();
	vector ang = ent.getViewAngles();
	vector boost = '0 0 0';
	if(skills_fac_standing || skills_fac_swimming)
		boost_x = (vel_x * sys.cos(ang_y)) + (vel_y * sys.cos(ang_y - 90));
	if(skills_fac_standing || skills_fac_swimming)
		boost_y = (vel_x * sys.sin(ang_y)) + (vel_y * sys.sin(ang_y - 90));
	if(skills_fac_swimming)
		boost_z = vel_z;
	boost *= mod;

	ent.applyImpulse(ent, 0, ent.getOrigin(), boost);
}

void player_skills_update_jump()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_jump");
	float mod = 25 * lv;
	if(lv == 0)
		return;

	vector vel = ent.getMove();
	vector boost = '0 0 0';
	if(vel_z > 0 && skills_fac_standing)
		boost_z = vel_z;
	boost *= mod;

	ent.applyImpulse(ent, 0, ent.getOrigin(), boost);
}

void player_skills_update_glide()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_glide");
	float mod = 100 * (1 + SKILLS_MAXLEVEL - lv);
	if(lv == 0)
		return;

	vector vel = ent.getLinearVelocity();
	if(vel_z < -mod)
	{
		vel_z = -mod;
		ent.setLinearVelocity(vel);
	}
}

void player_skills_update_heal()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_heal");
	float mod = 1 * lv;
	if(lv == 0)
		return;

	if(sys.getTime() > skills_timer_heal)
	{
		vector vel = ent.getMove();
		if(vel_x == 0 && vel_y == 0 && vel_z == 0)
		{
			float hl = ent.getHealth();
			hl = bound(hl + mod, 0, 100);
			ent.setHealth(hl);
		}
		skills_timer_heal = sys.getTime() + 1;
	}
}

void player_skills_update_knockout()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_knockout");
	float mod = lv / SKILLS_MAXLEVEL;
	if(lv == 0)
		return;

	float hl = ent.getHealth();
	if(hl < skills_fac_health && (hl / 100) < mod)
	{
		entity ent_find;
		do
		{
			ent_find = sys.getNextEntity("AIUse", "AIUSE_PERSON", ent_find);
			if(ent_find != $null_entity && ent.distanceTo(ent_find) <= 50)
			{
				ai ai_find = ent_find;
				if(ai_find.isEnemy(ent) && !ai_find.AI_DEAD && !ai_find.AI_KNOCKEDOUT && ai_find.AI_AlertLevel >= SKILLS_ALERTLEVEL_ATTACK)
					ai_find.KO_Knockout(ent);
			}
		}
		while(ent_find != $null_entity);
	}
}

void player_skills_update_extinguish()
{
	player ent = $player1;
	float lv = ent.getFloatKey("augmentations_extinguish");
	float mod = 50 * lv;
	if(lv == 0)
		return;

	entity ent_find;
	do
	{
		ent_find = sys.getNextEntity("AIUse", "AIUSE_LIGHTSOURCE", ent_find);
		if(ent_find != $null_entity && ent.distanceTo(ent_find) <= mod)
			ent_find.extinguishLights();
	}
	while(ent_find != $null_entity);
}

void player_skills_upgrade(string aug)
{
	player ent = $player1;
	float lv = ent.getFloatKey(aug);
	lv = bound(lv + 1, 0, SKILLS_MAXLEVEL);
	ent.setKey(aug, lv);
}

void player_skills()
{
	sys.waitFrame();
	player ent = $player1;
	skills_gui = ent.createOverlay("guis/player_skills.gui", 1);

	// Init the level keys
	ent.setKey("augmentations_crosshair", 0);
	ent.setKey("augmentations_unlock", 0);
	ent.setKey("augmentations_sonar", 0);
	ent.setKey("augmentations_infrared", 0);
	ent.setKey("augmentations_stealth", 0);
	ent.setKey("augmentations_soothing", 0);
	ent.setKey("augmentations_speed", 0);
	ent.setKey("augmentations_jump", 0);
	ent.setKey("augmentations_glide", 0);
	ent.setKey("augmentations_heal", 0);
	ent.setKey("augmentations_knockout", 0);
	ent.setKey("augmentations_extinguish", 0);

	// Upgrade all augs to max level for testing purposes, this will be done by using upgrade items
	float i;
	for(i = 1; i <= 4; i++)
	{
		player_skills_upgrade("augmentations_crosshair");
		player_skills_upgrade("augmentations_unlock");
		player_skills_upgrade("augmentations_sonar");
		player_skills_upgrade("augmentations_infrared");
		player_skills_upgrade("augmentations_stealth");
		player_skills_upgrade("augmentations_soothing");
		player_skills_upgrade("augmentations_speed");
		player_skills_upgrade("augmentations_jump");
		player_skills_upgrade("augmentations_glide");
		player_skills_upgrade("augmentations_heal");
		player_skills_upgrade("augmentations_knockout");
		player_skills_upgrade("augmentations_extinguish");
	}

	while(1)
	{
		sys.waitFrame();

		// Call the update function of each effect
		player_skills_update_crosshair();
		player_skills_update_unlock();
		player_skills_update_sonar();
		player_skills_update_infrared();
		player_skills_update_stealth();
		player_skills_update_soothing();
		player_skills_update_speed();
		player_skills_update_jump();
		player_skills_update_glide();
		player_skills_update_heal();
		player_skills_update_knockout();
		player_skills_update_extinguish();

		// Update skill factors so current values can be used in the next call
		player_skills_fac_set();
	}
}

 

hud_1.jpg.407fb6304a893a13b7bef175c5d5f908.jpghud_2.jpg.533835fe9b2ab5a21bd9427d73c99942.jpg

Link to post
Share on other sites

There's a spawnarg for an AI's melee skill level, no idea whether there's a ranged skill level. Note that the search function also works in the entity inspector.

For unlocking chests, you could try to find & unlock the lid instead of the froblock body, depending on which one is carrying the lockpicking spawnargs.

Link to post
Share on other sites

Another set of ideas and improvements I'm very happy with landed today; As suggested, the topmost information (starting at aug level two) now contains the melee skill as well as the ranged skill (accessed via AI spawnargs "def_melee_set" and "def_projectile"). They still use the internal technical names but it's better than nothing!

hud.jpg.48e7a396047430d6fc69183874109611.jpg

Speed and jump enhancement were merged. In the empty slot created a new aug was added: Path markers! A small glare effect (tdm_moon_glare.prt) is added to each path_corner on the map, allowing the player to see which spots are frequently patrolled by AI and what their routes are. This makes a great companion for the sonar as a visual cousin to it. At lower levels only paths that are frequently used will show up, while at max level all of them will... this is determined by the number of targets that path_corner has as follows: Level 4 = 4 or more targets, Level 3 = 3 or more targets, Level 2 = 2 or more targets, Level 1 = 1 or more targets (meaning every valid path_corner). Here's how they will look like:

testing_2020-12-29_17_16_27.thumb.jpg.cf376755b5d54014cc8dc0363524553f.jpg

Now that a final set of functionalities seems to have been determined, I can start associating each one to a limb. Like I said when a body part reaches 0 health, associated augs are permanently uninstalled and need to be installed again after their limb has been healed to allow it. The most logical arrangement I'm going to go for:

  • Torso 1: Enemy knockout
  • Torso 2: Enemy soothing
  • Torso 3: Healing
  • Torso 4: Stealth
  • Head 1: Crosshair, enemy info
  • Head 2: Path markers
  • Head 3: Loot sonar
  • Head 4: Infrared vision
  • Arm Left: Flame extinguishing
  • Arm Right: Door Unlocking
  • Leg Left: Glide (slow fall)
  • Leg Right: Speed + jump enhancement
  • Like 1
Link to post
Share on other sites
Quote

while further encouraging the player to pay attention to their limb damage on top of the little penalties already associated with it.

What is the overall purpose of making players pay attention to something that they have limited-to-no control over?  Can players shield individual limbs from damage if they notice one is low?  Do players have to choose what limb to heal? 

 

Link to post
Share on other sites
6 minutes ago, Springheel said:

What is the overall purpose of making players pay attention to something that they have limited-to-no control over?  Can players shield individual limbs from damage if they notice one is low?  Do players have to choose what limb to heal? 

 

Technically kind of: Limb damage is taken based on the player's velocity at the time of damage, with nearly none taken if damaged while standing still. This means that how you move while fighting an enemy AI or walking through a damage zone (eg: lava) determines which limbs are hurt the most: Strafing sideways exposes the arms, moving back and forth exposes the chest, jumping exposes the head (when going up) and legs (when falling down)... holding an object offers added protection to the torso at the detriment of the arms, while crouching or touching the ground protects the legs. Also fall damage will always affect the legs and slightly the arms... this is a natural consequence emerging from the proper patterns, but it gives the player knowledge of what to expect if risking a fall from a height (before getting the glide augmentation).

Healing is automated based on the sensitivity of each limb at the moment. If someday I add a GUI component, I may allow customizing the distribution or choosing which limb to use a health potion on... this one is not something I plan on doing anytime soon though.

Link to post
Share on other sites

Combat is usually quite difficult already.  Do you expect players to factor in what direction they are moving while in the middle of combat?  It's challenging enough just getting the parry right, let alone checking your HUD to see where you are damaged and calculating, "Oh, my torso is damaged so I better strafe to the side just in case my next parry fails so he hits my arm instead".  And since you usually can only survive 2 or 3 hits, crouching during combat would probably result in immediate death, regardless of how much you want to protect your legs.

While I can see the value in some of the augmentation effects as potions or other consumables, I'm not sure I see the point in tying them to a hit location system.

 

 

 

Link to post
Share on other sites

"def_projectile" does not refer to the ai's ranged skill but refers to the definition of the entity to use as ranged ammo.

FM's: Builder Roads, Old Habits, Old Habits Rebuild

Mapping and Scripting: Apples and Peaches

Sculptris Models and Tutorials: Obsttortes Models

My wiki articles: Obstipedia

Texture Blending in DR: DR ASE Blend Exporter

Link to post
Share on other sites
4 hours ago, Springheel said:

Combat is usually quite difficult already.  Do you expect players to factor in what direction they are moving while in the middle of combat?  It's challenging enough just getting the parry right, let alone checking your HUD to see where you are damaged and calculating, "Oh, my torso is damaged so I better strafe to the side just in case my next parry fails so he hits my arm instead".  And since you usually can only survive 2 or 3 hits, crouching during combat would probably result in immediate death, regardless of how much you want to protect your legs.

While I can see the value in some of the augmentation effects as potions or other consumables, I'm not sure I see the point in tying them to a hit location system.

It's mainly for logical reasons: I wanted each effect to be tied to a limb... if that limb is damaged to the point where it stops working, the augs on it should fail too.

Upon second thought however, I think permanently losing the augmentation when a limb reaches 0 is too big of a penalty, and would encourage players to fallback to a previous save just to not lose their upgrades. So for now I decided to simply disable augs when their limbs reach 0 health, but have them come back once the limb has health in it again.

29 minutes ago, Obsttorte said:

"def_projectile" does not refer to the ai's ranged skill but refers to the definition of the entity to use as ranged ammo.

I see. It seems to work just as fine though: It informs the player what the AI can throw at them, which is the information that matters in the end.

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...