Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Custom Enemies

A topic by Ada18980 created Jun 25, 2024 Views: 447
Viewing posts 1 to 1
(3 edits)

For something simple, like a zombie that chases the player and adds existing restraints (we’ll have it add maid restraints as an example), you just need: -A sprite (place a .png in the Enemies/ folder) -A few text entries in Text_KinkyDungeon.csv -An entry in KinkyDungeonEnemiesList.js

Step 1: adding a sprite

This process is entirely up to you on how you do it. I use Aseprite (paid) to edit files but you can use Paint.net (free), graphics gale (free), or even just MS paint. There are also online pixel art tools available. The only restriction is that the sprite must be 72x72 and the game does adhere to a strict chibi style, so you may want to use something like Aseprite or Graphics Gale or some other pixel art centric program that lets you use a limited color palette.

Step 2: Text entries

This part is also really simple. All enemies need the following two entries by default. Our enemy will be called MaidZombie in the code, but the name in the code is different from the name that appears ingame. The NameENEMYNAME entry determines the name in the game, and the KillENEMYNAME entry shows when the enemy is defeated.

NameMaidZombie,Maid Zombie
KillMaidZombie,"The maid zombie does a short curtsey and runs away."

Next, you will need entries for the enemy attacking you. This part is similar, but you have to leave RestraintAdded and DamageDealt unchanged in the examples as these specific keywords are replaced with the actual values ingame.

AttackMaidZombieBind,"The maid zombie binds you! (+RestraintAdded) (DamageTaken)"
AttackMaidZombie,"The maid zombie tickles you! (DamageTaken)"

Note that AttackENEMYNAMEBind is what shows when the enemy applies a restraint, and AttackENEMYNAME is simply what happens when the enemy can’t apply a restraint (because you are too bound or too high stamina). Keep the text in quotes to avoid issues with commas breaking the csv system.

Step 3: JSON

This is the most technically complicated part. If you use an IDE like Visual Studio Code (which I highly recommend, it’s basically a free text editor except you can hover over fields to get a description of what they do), it’s a little easier. But if you don’t use an IDE, you can check KDTypeDefs.ts (specifically the enemy entry) for some limited descriptions.

Anyway, let’s look at our JSON entry for a basic enemy:

  // Name: Self explanatory
  // bound: The sprite in EnemiesBound/ that the enemy uses when the binding bar goes to max. This also marks the enemy as humanoid (so they can help you out of binds) and bindable.
  // playLine: The enemy's 'voice' basically. Search 'KinkyDungeonRemindJailPlayTemplate0' in the text definitions if you want to make your own. Replace 'Template' with your playLine
  // clusterWith: When this enemy is created, it will have a chance to try to create enemies that share a tag defined by clusterWith.
  // In this case, it will try to generate other enemies with the zombie tag.
  {name: "MaidZombie", bound: "Zombie", playLine: "Zombie", clusterWith: "zombie",

 
  // followRange: The distance the AI will try to approach the player to. 1 is melee. 2, 2.5, 3, all work for a ranged enemy.
  // AI: There are several AI types.
    // hunt means the enemy will search for the player and chase.
    // wander means the enemy will move around but wont chase.
    // guard means the enemy will hold position and chase the player, but only to about 2x the vision range
  // guardChance: This enemy has a 40% chance of spawning with the 'guard' AI instead of the 'hunt' AI.
  followRange: 1, AI: "hunt", guardChance: 0.4,
  // These are the enemy's tags. Check other enemies to get an idea of what values to put here.
  // Of note is that 'ignoreharmless' makes the enemy ignore you when you are out of SP
  // Replace 'ignoreharmless' with 'leashing' if you want the enemy to leash the player to jail
  // 'opendoors' allows the enemy to open doors
  // 'closedoors' mmakes the enemy sometimes close doors behind herself
  // 'ribbonRestraints' means the enemy will use ribbon restraints. This is a tag declared on the ribbon restraints themselves
  tags: KDMapInit(["ignoreharmless", "zombie", "melee", "fireweakness", "ribbonRestraints", "meleeweakness"]),
  // evasion: Positive values improve evasiveness (formula for hit chance is 1/(1+evasion)). Negative values work the opposite way, multiplying the hit chance by (1 - evasion)
  // armor: flat value subtracted from attacks. Note that all hp, armor values are multiplied by 10 ingame
  // maxhp: enemy hp. Multiplied by 10, so this zombie will have 100 hp displayed ingame
  // visionRadius: sight radius. 5 is for nearsighted enemies, 8 is for observant, 9 and 10 are truly extraordinary/bosses
  evasion: -1, armor: 1, maxhp: 10, visionRadius: 5, 
  // minLevel: Minimum floor enemy appears on
  // weight: basic weight for spawning the enemy. The final enemy selected to put on a square is a weighted average of every enemy in the game
  minLevel:1, weight:6,
  // movePoints: How many turns between moves. 3 means that the enemy waits 2 turns between moves. 1 means it moves every turn.
  // attackPoints: How many turns per attack. 3 means the enemy takes 3 turns to attack the player. 1 means it's instant.
  // attack: type of attack. This is just a string, so you add the keywords together
    // Melee means the enemy will use warning tiles. Spell means the enemy will cast spells. These are not exclusive
    // Bind means the enemy will bind the player if it hits
    // Will means the enemy will drain Willpower (stamina, old name for it in the code) even if it binds you. Otherwise it will only drain fullBoundBonus sp on a hit if the player is fully bound
    // Lock makes the enemy apply one lock if able, instead of binding. LockAll makes the enemy lock all eligible restraints at once.
  // attackWidth: Attack width, basically how much to the side the attack goes. Decimal values are OK. Note that only one target will be hit regardless of area size.
  // attackRange: Range of the melee attack. This can be used for ranged melee attacks
  // tilesMinRange: This is how much the attack range is 'offset' forward. Usually it is set to 1 for enemies with wide attacks so the attack doesn't reach behind them.
  // power: Damage the enemy does. If attack type includes 'Will' it will do this damage every turn it hits the player
  // dmgType: Damage type. Different types have different effects on stamina, distraction, mana, etc
  // fullBoundBonus: This is added to the power if the player can't be further bound, dealing bonus damage
  movePoints: 3, attackPoints: 3, attack: "MeleeBind", attackWidth: 1, attackRange: 1, power: 1, dmgType: "grope", fullBoundBonus: 3,
  // terrainTags are modifiers to the enemy's spawn weight.
    // secondhalf and lastthird are sort of legacy. secondhalf applies on floors with a single digit ending in 2, 3, 4, 5, while lastthird applies on 4, 5
    // some enemies have something like 'conjureRage' or 'ropeAnger'. Anger is the less serious version of rage, and basically this is used to make some enemies appear more when a goddess is low rep
  terrainTags: {"secondhalf":10, "lastthird":14},
  // These are the tilesets from KinkyDungeonMapParams.js that the enemy appears on. It is a list[] converted to a map on runtime.
  // You can replace 'floors' with 'allFloors: true' to make her appear on all tilesets
  floors:KDMapInit(["grv"]),
  // This is a list [] of loot. Add multiple entries if desired, follow the format as in other enemies. This one drops only gold.
  dropTable: [{name: "Gold", amountMin: 5, amountMax: 10, weight: 10}]},

Here’s a few extra, advanced tags:

spells: [] -- a list of spell names. The enemy chooses one at random (that it can cast), and you have to add 'Spell' to its attack: "" field

followLeashedOnly: boolean -- This confusingly named tags causes the enemy to, IF the player is being leashed, only FOLLOW her. As opposed to casting spells, etc, at her (and possibly the leasher) as she is being dragged off.

spellRdy: boolean -- Shows a spell indicator when the enemy's spell cooldown is ready

spellCooldownMult: 2, spellCooldownMod: 1 -- Normally enemies use the manacost of a spell to determine spell cooldown. This lets some enemies made modifications to all of their spells (multiplying first, then modifying)

spellResist: number -- Spell resistance is similar to evasion but instead of multiplying hit chance, simply multiplies damage taken from magic damage types. It's similar to the magicresist tag but allows for more control. 

projectileTargeting: boolean -- This makes it so the enemy will set its followRange to 1 if the player is blocked. This is used for ranged attackers including spellcasters to make them walk forward instead of standing there

projectileAttack: boolean -- This makes it so an enemy can't attack you from behind a wall. Meant to reduce frustration from being hit by a shuriken through a wall or being pulled through a wall by a frog's tongue

dontKiteWhenDisabled: boolean -- This makes it so the enemy stops kiting the player when the player is stunned. Maidforce ninja uses this behavior so she moves in to bind when her stun grenade hits you, but backs off when it wears off (she has followRange: 1 and kite: 1.5)

kite: number -- This makes it so an enemy kites you until it's greater than the specified distance. 1.5 is sufficient for most enemies but some have a longer minimum range on their spells (like enemies who shoot rubber bullets) 

blindSight: number -- This is similar to vision radius but also sees thru walls
alwaysEvade: boolean -- This is used on ghosts to make them always dodge melee attacks if they aren't made with a magic weapon

bypass: boolean -- makes an enemy slip things like plugs, etc, underneath chastity belts

summon -- is a complex tag that allows you to spawn additional enemies when the enemy is created at mapgen. See the ropekraken for example

ondeath -- see the mushrooms for an example of casting a spell on death, see the bigslime for example of creating additional enemies ondeath. You can also use it to trigger a dialogue (see Fuuka) or to start or end a quest (see the DragonLeaderDuelist)

specialdialogue: string-- starts a dialogue when you click on her instead of the generic ally dialogue. Generally used on enemies that belong to an always neutral faction, like Prisoner or Player. If you use it on an enemy faction then you still cant talk to them while they are hostile, you will have to use a dialoguetrigger instead which is a different can of worms but let me know if you'd rather have a tag that overrides this (allowspecialdialoguewhilehostile)