Files
Bubberstation/code/datums/ai/monkey/monkey_behaviors.dm
carlarctg 66b8748091 Adds Summon Simians & Buffs/QoLs Mutate (#77196)
## About The Pull Request

Adds Summon Simians, a spell that summons four monkeys or lesser
gorillas, with the amount increasing per upgrade. The monkeys have
various fun gear depending on how lucky you get and how leveled the
spell is. If the spell is maximum level, it only summons normal
gorillas.

Added further support for nonhuman robed casting: Monkeys, cyborgs, and
drones can all now cast robed spells as long as they're wearing a
wizardly hat as well.

Made monkeys able to wield things again.

Wizard Mutate spell works on non-human races. It also gives you
Gigantism now (funny). If the Race can't support tinted bodyparts, your
whole sprite is temporarily turned green.

Made Laser eyes projectiles a subtype of actual lasers, which has
various properties such as on-hit effects and upping the damage to 30.

Improved some monkey AI code.

## Why It's Good For The Game

> Adds Summon Simians, a spell that summons four monkeys or lesser
gorillas, with the amount increasing per upgrade. The monkeys have
various fun gear depending on how lucky you get and how leveled the
spell is. If the spell is maximum level, it only summons normal
gorillas.

It's criminal we don't have a monky spell, and this is a really fun spin
on it. Total chaos, but total monky chaos. It's surprisingly strong,
but! it can very well backfire if you stay near the angry monkeys too
long and your protection fades away. Unless you become a monkey
yourself!!

> Wizard Mutate spell works on non-human races. 

This spell is great but it's hampered by the mutation's human
requirement, which is reasonable in normal gameplay. Wizards don't need
to care about that, and the human restriction hinders a lot of possible
gimmicks, so off it goes. Also, wizard hulk does't cause chunky fingers
for similar reasons

> Made Laser eyes projectiles a subtype of actual lasers, which has
various properties such as on-hit effects and upping the damage to 30.

Don't really caer about the damage so much, this is more so that it has
effects such as on-hit visuals. Can lower the damage if required, but
honestly anything that competes against troll mjolnir is good.

> Added further support for nonhuman robed casting: Monkeys, cyborgs,
and drones can all now cast robed spells as long as they're wearing a
wizardly hat as well.

SS13 is known for 'The Dev Team Thinks of Everything' and I believe this
is a sorely lacking part of this or something. It's funny.
I want to see a monkey wizard.

> Made monkeys able to wield things again.

I really don't know why this was a thing and it was breaking my axe and
spear wielding primal monkeys. Like, why?

## Changelog

🆑
add: Adds Summon Simians, a spell that summons four monkeys or lesser
gorillas, with the amount increasing per upgrade. The monkeys have
various fun gear depending on how lucky you get and how leveled the
spell is. If the spell is maximum level, it only summons normal
gorillas.
balance: Wizard Mutate spell works on non-human races. It also gives you
Gigantism now (funny). If the Race can't support tinted bodyparts, your
whole sprite is temporarily turned green.
balance: Made Laser eyes projectiles a subtype of actual lasers, which
has various properties such as on-hit effects and upping the damage to
30.
add: Added further support for nonhuman robed casting: Monkeys, cyborgs,
and drones can all now cast robed spells as long as they're wearing a
wizardly hat as well.
balance: Made monkeys able to wield two-handed things again.
/🆑

---------

Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
2023-08-09 21:28:46 +01:00

330 lines
13 KiB
Plaintext

/datum/ai_behavior/battle_screech/monkey
screeches = list("roar","screech")
/datum/ai_behavior/monkey_equip
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_REQUIRE_REACH
/datum/ai_behavior/monkey_equip/finish_action(datum/ai_controller/controller, success)
. = ..()
if(!success) //Don't try again on this item if we failed
controller.set_blackboard_key_assoc(BB_MONKEY_BLACKLISTITEMS, controller.blackboard[BB_MONKEY_PICKUPTARGET], TRUE)
controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET)
/datum/ai_behavior/monkey_equip/proc/equip_item(datum/ai_controller/controller)
var/mob/living/living_pawn = controller.pawn
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
var/best_force = controller.blackboard[BB_MONKEY_BEST_FORCE_FOUND]
if(!isturf(living_pawn.loc))
finish_action(controller, FALSE)
return
if(!target)
finish_action(controller, FALSE)
return
if(target.anchored) //Can't pick it up, so stop trying.
finish_action(controller, FALSE)
return
// Strong weapon
else if(target.force > best_force)
living_pawn.drop_all_held_items()
living_pawn.put_in_hands(target)
controller.set_blackboard_key(BB_MONKEY_BEST_FORCE_FOUND, target.force)
finish_action(controller, TRUE)
return
else if(target.slot_flags) //Clothing == top priority
living_pawn.dropItemToGround(target, TRUE)
living_pawn.update_icons()
if(!living_pawn.equip_to_appropriate_slot(target))
finish_action(controller, FALSE)
return //Already wearing something, in the future this should probably replace the current item but the code didn't actually do that, and I dont want to support it right now.
finish_action(controller, TRUE)
return
// EVERYTHING ELSE
else if(living_pawn.get_empty_held_indexes())
living_pawn.put_in_hands(target)
finish_action(controller, TRUE)
return
finish_action(controller, FALSE)
/datum/ai_behavior/monkey_equip/ground
required_distance = 0
/datum/ai_behavior/monkey_equip/ground/perform(seconds_per_tick, datum/ai_controller/controller)
. = ..()
equip_item(controller)
/datum/ai_behavior/monkey_equip/pickpocket
/datum/ai_behavior/monkey_equip/pickpocket/perform(seconds_per_tick, datum/ai_controller/controller)
. = ..()
if(controller.blackboard[BB_MONKEY_PICKPOCKETING]) //We are pickpocketing, don't do ANYTHING!!!!
return
INVOKE_ASYNC(src, PROC_REF(attempt_pickpocket), controller)
/datum/ai_behavior/monkey_equip/pickpocket/proc/attempt_pickpocket(datum/ai_controller/controller)
var/obj/item/target = controller.blackboard[BB_MONKEY_PICKUPTARGET]
var/mob/living/victim = target.loc
var/mob/living/living_pawn = controller.pawn
if(!istype(victim))
finish_action(controller, FALSE)
return
victim.visible_message(span_warning("[living_pawn] starts trying to take [target] from [victim]!"), span_danger("[living_pawn] tries to take [target]!"))
controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, TRUE)
var/success = FALSE
if(do_after(living_pawn, MONKEY_ITEM_SNATCH_DELAY, victim) && target && living_pawn.CanReach(victim))
for(var/obj/item/I in victim.held_items)
if(I == target)
victim.visible_message(span_danger("[living_pawn] snatches [target] from [victim]."), span_userdanger("[living_pawn] snatched [target]!"))
if(victim.temporarilyRemoveItemFromInventory(target))
if(!QDELETED(target) && !equip_item(controller))
target.forceMove(living_pawn.drop_location())
success = TRUE
break
else
victim.visible_message(span_danger("[living_pawn] tried to snatch [target] from [victim], but failed!"), span_userdanger("[living_pawn] tried to grab [target]!"))
finish_action(controller, success) //We either fucked up or got the item.
/datum/ai_behavior/monkey_equip/pickpocket/finish_action(datum/ai_controller/controller, success)
. = ..()
controller.set_blackboard_key(BB_MONKEY_PICKPOCKETING, FALSE)
controller.clear_blackboard_key(BB_MONKEY_PICKUPTARGET)
/datum/ai_behavior/monkey_flee
/datum/ai_behavior/monkey_flee/perform(seconds_per_tick, datum/ai_controller/controller)
. = ..()
var/mob/living/living_pawn = controller.pawn
if(living_pawn.health >= MONKEY_FLEE_HEALTH)
finish_action(controller, TRUE) //we're back in bussiness
return
var/mob/living/target = null
// flee from anyone who attacked us and we didn't beat down
for(var/mob/living/L in view(living_pawn, MONKEY_FLEE_VISION))
if(controller.blackboard[BB_MONKEY_ENEMIES][L] && L.stat == CONSCIOUS)
target = L
break
if(target)
SSmove_manager.move_away(living_pawn, target, max_dist=MONKEY_ENEMY_VISION, delay=5)
else
finish_action(controller, TRUE)
/datum/ai_behavior/monkey_attack_mob
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
/datum/ai_behavior/monkey_attack_mob/setup(datum/ai_controller/controller, target_key)
. = ..()
set_movement_target(controller, controller.blackboard[target_key])
/datum/ai_behavior/monkey_attack_mob/perform(seconds_per_tick, datum/ai_controller/controller, target_key)
. = ..()
var/mob/living/target = controller.blackboard[target_key]
var/mob/living/living_pawn = controller.pawn
if(!target || target.stat != CONSCIOUS)
finish_action(controller, TRUE) //Target == owned
return
if(isturf(target.loc) && !IS_DEAD_OR_INCAP(living_pawn)) // Check if they're a valid target
// check if target has a weapon
var/obj/item/W
for(var/obj/item/I in target.held_items)
if(!(I.item_flags & ABSTRACT))
W = I
break
// if the target has a weapon, chance to disarm them
if(W && SPT_PROB(MONKEY_ATTACK_DISARM_PROB, seconds_per_tick))
monkey_attack(controller, target, seconds_per_tick, TRUE)
else
monkey_attack(controller, target, seconds_per_tick, FALSE)
/datum/ai_behavior/monkey_attack_mob/finish_action(datum/ai_controller/controller, succeeded, target_key)
. = ..()
var/mob/living/living_pawn = controller.pawn
SSmove_manager.stop_looping(living_pawn)
controller.clear_blackboard_key(target_key)
/// attack using a held weapon otherwise bite the enemy, then if we are angry there is a chance we might calm down a little
/datum/ai_behavior/monkey_attack_mob/proc/monkey_attack(datum/ai_controller/controller, mob/living/target, seconds_per_tick, disarm)
var/mob/living/living_pawn = controller.pawn
if(living_pawn.next_move > world.time)
return
living_pawn.changeNext_move(CLICK_CD_MELEE) //We play fair
var/obj/item/weapon = locate(/obj/item) in living_pawn.held_items
living_pawn.face_atom(target)
living_pawn.set_combat_mode(TRUE)
if(isnull(controller.blackboard[BB_MONKEY_GUN_WORKED]))
controller.set_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE)
// attack with weapon if we have one
if(living_pawn.CanReach(target, weapon))
if(weapon)
weapon.melee_attack_chain(living_pawn, target)
else
living_pawn.UnarmedAttack(target, null, disarm ? list("right" = TRUE) : null) //Fake a right click if we're disarmin
controller.set_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) // We reset their memory of the gun being 'broken' if they accomplish some other attack
else if(weapon)
var/atom/real_target = target
if(prob(10)) // Artificial miss
real_target = pick(oview(2, target))
var/obj/item/gun/gun = locate() in living_pawn.held_items
var/can_shoot = gun?.can_shoot() || FALSE
if(gun && controller.blackboard[BB_MONKEY_GUN_WORKED] && prob(95))
// We attempt to attack even if we can't shoot so we get the effects of pulling the trigger
gun.afterattack(real_target, living_pawn, FALSE)
controller.set_blackboard_key(BB_MONKEY_GUN_WORKED, can_shoot ? TRUE : prob(80)) // Only 20% likely to notice it didn't work
if(can_shoot)
controller.set_blackboard_key(BB_MONKEY_GUN_NEURONS_ACTIVATED, TRUE)
else
living_pawn.throw_item(real_target)
controller.set_blackboard_key(BB_MONKEY_GUN_WORKED, TRUE) // 'worked'
// no de-aggro
if(controller.blackboard[BB_MONKEY_AGGRESSIVE])
return
// we've queued up a monkey attack on a mob which isn't already an enemy, so give them 1 threat to start
// note they might immediately reduce threat and drop from the list.
// this is fine, we're just giving them a love tap then leaving them alone.
// unless they fight back, then we retaliate
if(isnull(controller.blackboard[BB_MONKEY_ENEMIES][target]))
controller.set_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, 1)
/// mob refs are uids, so this is safe
if(SPT_PROB(MONKEY_HATRED_REDUCTION_PROB, seconds_per_tick))
controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, target, -1)
// if we are not angry at our target, go back to idle
if(controller.blackboard[BB_MONKEY_ENEMIES][target] <= 0)
controller.remove_thing_from_blackboard_key(BB_MONKEY_ENEMIES, target)
if(controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET] == target)
finish_action(controller, TRUE)
/datum/ai_behavior/disposal_mob
behavior_flags = AI_BEHAVIOR_REQUIRE_MOVEMENT | AI_BEHAVIOR_MOVE_AND_PERFORM //performs to increase frustration
/datum/ai_behavior/disposal_mob/setup(datum/ai_controller/controller, attack_target_key, disposal_target_key)
. = ..()
set_movement_target(controller, controller.blackboard[attack_target_key])
/datum/ai_behavior/disposal_mob/finish_action(datum/ai_controller/controller, succeeded, attack_target_key, disposal_target_key)
. = ..()
controller.clear_blackboard_key(attack_target_key) //Reset attack target
controller.set_blackboard_key(BB_MONKEY_DISPOSING, FALSE) //No longer disposing
controller.clear_blackboard_key(disposal_target_key) //No target disposal
/datum/ai_behavior/disposal_mob/perform(seconds_per_tick, datum/ai_controller/controller, attack_target_key, disposal_target_key)
. = ..()
if(controller.blackboard[BB_MONKEY_DISPOSING]) //We are disposing, don't do ANYTHING!!!!
return
var/mob/living/target = controller.blackboard[attack_target_key]
var/mob/living/living_pawn = controller.pawn
set_movement_target(controller, target)
if(!target)
finish_action(controller, FALSE)
return
if(target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(target.pulledby, /datum/ai_controller/monkey)) //Dont steal from my fellow monkeys.
if(living_pawn.Adjacent(target) && isturf(target.loc))
target.grabbedby(living_pawn)
return //Do the rest next turn
var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
set_movement_target(controller, disposal)
if(!disposal)
finish_action(controller, FALSE)
return
if(living_pawn.Adjacent(disposal))
INVOKE_ASYNC(src, PROC_REF(try_disposal_mob), controller, attack_target_key, disposal_target_key) //put him in!
else //This means we might be getting pissed!
return
/datum/ai_behavior/disposal_mob/proc/try_disposal_mob(datum/ai_controller/controller, attack_target_key, disposal_target_key)
var/mob/living/living_pawn = controller.pawn
var/mob/living/target = controller.blackboard[attack_target_key]
var/obj/machinery/disposal/disposal = controller.blackboard[disposal_target_key]
controller.set_blackboard_key(BB_MONKEY_DISPOSING, TRUE)
if(target && disposal?.stuff_mob_in(target, living_pawn))
disposal.flush()
finish_action(controller, TRUE, attack_target_key, disposal_target_key)
/datum/ai_behavior/recruit_monkeys/perform(seconds_per_tick, datum/ai_controller/controller)
. = ..()
controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN)
var/mob/living/living_pawn = controller.pawn
for(var/mob/living/nearby_monkey in view(living_pawn, MONKEY_ENEMY_VISION))
if(!HAS_AI_CONTROLLER_TYPE(nearby_monkey, /datum/ai_controller/monkey))
continue
if(!SPT_PROB(MONKEY_RECRUIT_PROB, seconds_per_tick))
continue
// Recruited a monkey to our side
controller.set_blackboard_key(BB_MONKEY_RECRUIT_COOLDOWN, world.time + MONKEY_RECRUIT_COOLDOWN)
// Other monkeys now also hate the guy we're currently targeting
nearby_monkey.ai_controller.add_blackboard_key_assoc(BB_MONKEY_ENEMIES, controller.blackboard[BB_MONKEY_CURRENT_ATTACK_TARGET], MONKEY_RECRUIT_HATED_AMOUNT)
finish_action(controller, TRUE)
/datum/ai_behavior/monkey_set_combat_target/perform(seconds_per_tick, datum/ai_controller/controller, set_key, enemies_key)
var/list/enemies = controller.blackboard[enemies_key]
var/list/valids = list()
for(var/mob/living/possible_enemy in view(MONKEY_ENEMY_VISION, controller.pawn))
if(possible_enemy == controller.pawn)
continue // don't target ourselves
if(!enemies[possible_enemy]) //We don't hate this creature! But we might still attack it!
if(!controller.blackboard[BB_MONKEY_AGGRESSIVE]) //We are not aggressive either, so we won't attack!
continue
if(faction_check(possible_enemy.faction, list(FACTION_MONKEY, FACTION_JUNGLE), exact_match = FALSE) && !controller.blackboard[BB_MONKEY_TARGET_MONKEYS]) // do not target your team. includes monkys gorillas etc.
continue
// Weighted list, so the closer they are the more likely they are to be chosen as the enemy
valids[possible_enemy] = CEILING(100 / (get_dist(controller.pawn, possible_enemy) || 1), 1)
if(!length(valids))
finish_action(controller, FALSE)
return
controller.set_blackboard_key(set_key, pick_weight(valids))
finish_action(controller, TRUE)