Files
Bubberstation/code/_onclick/ai.dm
LemonInTheDark 5c927e406b Makes AI tracking more snappy, improves API (AI movement change) (#81401)
## About The Pull Request

Ok so tracking (from the datum) worked, but when used to follow someone
it had a noticable delay from the datum needing to wait for process to
fire to do its work

This would be an expensive proc to run constantly, but we don't really
have to (there are not that many ai eyes in the world). So rather then
only processing to keep step, let's track the target mob by its
movement, and then fall back on a process loop to handle rechecking in
case of camera memes.

This does technically mean you won't "break" the track if the cameras go
out until the tracked mob moves, but I think that's a reasonable price
to pay for more responsive movement. I think I could make our current
system work with it too, though it would be a bit more wasteful. John if
you have opinions just lay into me.

I've also renamed/pulled apart the helper procs for the trackable datum,
with the hope of making how they are used more understandable at a
glance

Oh and rather then holding a weakref since I needed MOVED anyway I just
use QDELETING to free the ref if the mob goes away

### Edit:

#### Glide size touchups
Implements glide size mirroring so we move at the same speed as our
target

Also moves the existing signal to send to the trackable datum itself, as
appears intended from the doc comment

#### AI behavior changes

Rewrites ai movement to be less dumb

OK so 2 things here. One is a behavior change, the other is a visual QOL
thing.

The way ai movement works is we move graduated "steps". Either moving 1,
2, or 3 steps per tick.
We do this by, so long as input is held down, incrementing a number
called "sprint"
Currently it'll go from 10 to 50 (formula effectively looks like steps =
(sprint / 20) + 1))

Anyway, this is... not fine but ok, but the way we handle deceleration
is ass IMO. It's literally just wait 0.5 seconds and sprint resets.
I think this feels crummy, so instead I've made it decay depending on
how long you go between inputs, at 7x greater rate then it increases.

That's the behavior change. Visual change is a lot easier.
Ais were not gliding properly. They assumed they had 4 ticks to move a
tile, rather then 1. This meant they'd jump around constantly, to catch
up to where we expect them to be.
I've fixed this by giving them 1 tick instead. Should feel a lot better

## Why It's Good For The Game

Snappier response times, cleaner code

## Changelog
🆑
add: AI's acceleration now smoothly decays, instead of just falling back
down to 0 after 0.5 seconds
fix: AI's standard movement (non accelerated) is smooth now, instead of
constantly jumping around
fix: AIs will now follow their targets more closely, shouldn't have any
issues with them lagging behind anymore
/🆑
2024-02-14 13:41:22 +01:00

270 lines
6.9 KiB
Plaintext

/*
AI ClickOn()
The AI can double click to move the camera (this was already true but is cleaner),
or double click a mob to track them.
Note that AI have no need for the adjacency proc, and so this proc is a lot cleaner.
*/
/mob/living/silicon/ai/DblClickOn(atom/A, params)
if(control_disabled || incapacitated())
return
if(ismob(A))
ai_tracking_tool.track_mob(src, A)
else
A.move_camera_by_click()
/mob/living/silicon/ai/ClickOn(atom/A, params)
if(world.time <= next_click)
return
next_click = world.time + 1
var/list/modifiers = params2list(params)
if(SEND_SIGNAL(src, COMSIG_MOB_CLICKON, A, modifiers) & COMSIG_MOB_CANCEL_CLICKON)
return
if(!can_interact_with(A))
return
if(multicam_on)
var/turf/T = get_turf(A)
if(T)
for(var/atom/movable/screen/movable/pic_in_pic/ai/P in T.vis_locs)
if(P.ai == src)
P.Click(params)
break
if(check_click_intercept(params,A))
return
if(control_disabled || incapacitated())
return
var/turf/pixel_turf = get_turf_pixel(A)
if(isnull(pixel_turf))
return
if(!can_see(A))
return
if(LAZYACCESS(modifiers, SHIFT_CLICK))
if(LAZYACCESS(modifiers, CTRL_CLICK))
CtrlShiftClickOn(A)
return
ShiftClickOn(A)
return
if(LAZYACCESS(modifiers, ALT_CLICK)) // alt and alt-gr (rightalt)
AltClickOn(A)
return
if(LAZYACCESS(modifiers, CTRL_CLICK))
CtrlClickOn(A)
return
if(LAZYACCESS(modifiers, MIDDLE_CLICK))
MiddleClickOn(A, params)
return
if(LAZYACCESS(modifiers, RIGHT_CLICK))
var/secondary_result = A.attack_ai_secondary(src, modifiers)
if(secondary_result == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN || secondary_result == SECONDARY_ATTACK_CONTINUE_CHAIN)
return
else if(secondary_result != SECONDARY_ATTACK_CALL_NORMAL)
CRASH("attack_ai_secondary did not return a SECONDARY_ATTACK_* define.")
if(world.time <= next_move)
return
if(waypoint_mode)
waypoint_mode = 0
set_waypoint(A)
return
A.attack_ai(src)
/*
AI has no need for the UnarmedAttack() and RangedAttack() procs,
because the AI code is not generic; attack_ai() is used instead.
The below is only really for safety, or you can alter the way
it functions and re-insert it above.
*/
/mob/living/silicon/ai/UnarmedAttack(atom/A, proximity_flag, list/modifiers)
A.attack_ai(src)
/mob/living/silicon/ai/RangedAttack(atom/A)
A.attack_ai(src)
/atom/proc/attack_ai(mob/user)
return
/**
* What happens when the AI holds right-click on an item. Returns a SECONDARY_ATTACK_* value.
*
* Arguments:
* * user The mob holding the right click
* * modifiers The list of the custom click modifiers
*/
/atom/proc/attack_ai_secondary(mob/user, list/modifiers)
return SECONDARY_ATTACK_CALL_NORMAL
/*
Since the AI handles shift, ctrl, and alt-click differently
than anything else in the game, atoms have separate procs
for AI shift, ctrl, and alt clicking.
*/
/mob/living/silicon/ai/CtrlShiftClickOn(atom/target)
target.AICtrlShiftClick(src)
/mob/living/silicon/ai/ShiftClickOn(atom/target)
target.AIShiftClick(src)
/mob/living/silicon/ai/CtrlClickOn(atom/target)
target.AICtrlClick(src)
/mob/living/silicon/ai/AltClickOn(atom/target)
target.AIAltClick(src)
/*
The following criminally helpful code is just the previous code cleaned up;
I have no idea why it was in atoms.dm instead of respective files.
*/
/* Questions: Instead of an Emag check on every function, can we not add to airlocks onclick if emag return? */
/* Atom Procs */
/atom/proc/AICtrlClick(mob/living/silicon/ai/user)
return
/atom/proc/AIAltClick(mob/living/silicon/ai/user)
AltClick(user)
return
/atom/proc/AIShiftClick(mob/living/silicon/ai/user)
return
/atom/proc/AICtrlShiftClick(mob/living/silicon/ai/user)
return
/* Airlocks */
/obj/machinery/door/airlock/AICtrlClick(mob/living/silicon/ai/user) // Bolts doors
if(obj_flags & EMAGGED)
return
toggle_bolt(user)
add_hiddenprint(user)
/obj/machinery/door/airlock/AIAltClick(mob/living/silicon/ai/user) // Eletrifies doors.
if(obj_flags & EMAGGED)
return
if(!secondsElectrified)
shock_perm(user)
else
shock_restore(user)
/obj/machinery/door/airlock/AIShiftClick(mob/living/silicon/ai/user) // Opens and closes doors!
if(obj_flags & EMAGGED)
return
user_toggle_open(user)
add_hiddenprint(user)
/obj/machinery/door/airlock/AICtrlShiftClick(mob/living/silicon/ai/user) // Sets/Unsets Emergency Access Override
if(obj_flags & EMAGGED)
return
toggle_emergency(user)
add_hiddenprint(user)
/////////////
/* APC */
/////////////
/// Toggle APC power settings
/obj/machinery/power/apc/AICtrlClick(mob/living/silicon/ai/user)
if(!can_use(user, loud = TRUE))
return
toggle_breaker(user)
/// Toggle APC environment settings (atmos)
/obj/machinery/power/apc/AICtrlShiftClick(mob/living/silicon/ai/user)
if(!can_use(user, loud = TRUE))
return
if(!is_operational || failure_timer)
return
environ = environ ? APC_CHANNEL_OFF : APC_CHANNEL_ON
if (user)
add_hiddenprint(user)
var/enabled_or_disabled = environ ? "enabled" : "disabled"
balloon_alert(user, "environment power [enabled_or_disabled]")
user.log_message("[enabled_or_disabled] the [src] environment settings", LOG_GAME)
update_appearance()
update()
/// Toggle APC lighting settings
/obj/machinery/power/apc/AIShiftClick(mob/living/silicon/ai/user)
if(!can_use(user, loud = TRUE))
return
if(!is_operational || failure_timer)
return
lighting = lighting ? APC_CHANNEL_OFF : APC_CHANNEL_ON
if (user)
var/enabled_or_disabled = lighting ? "enabled" : "disabled"
add_hiddenprint(user)
balloon_alert(user, "lighting power toggled [enabled_or_disabled]")
user.log_message("turned [enabled_or_disabled] the [src] lighting settings", LOG_GAME)
update_appearance()
update()
/// Toggle APC equipment settings
/obj/machinery/power/apc/AIAltClick(mob/living/silicon/ai/user)
if(!can_use(user, loud = TRUE))
return
if(!is_operational || failure_timer)
return
equipment = equipment ? APC_CHANNEL_OFF : APC_CHANNEL_ON
if (user)
var/enabled_or_disabled = equipment ? "enabled" : "disabled"
balloon_alert(user, "equipment power toggled [enabled_or_disabled]")
add_hiddenprint(user)
user.log_message("turned [enabled_or_disabled] the [src] equipment settings", LOG_GAME)
update_appearance()
update()
/obj/machinery/power/apc/attack_ai_secondary(mob/living/silicon/user, list/modifiers)
if(!can_use(user, loud = TRUE))
return
togglelock(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/* AI Turrets */
/obj/machinery/turretid/AIAltClick(mob/living/silicon/ai/user) //toggles lethal on turrets
if(ailock)
return
toggle_lethal(user)
/obj/machinery/turretid/AICtrlClick(mob/living/silicon/ai/user) //turns off/on Turrets
if(ailock)
return
toggle_on(user)
/* Holopads */
/obj/machinery/holopad/AIAltClick(mob/living/silicon/ai/user)
if (user)
balloon_alert(user, "disrupted all active calls")
add_hiddenprint(user)
hangup_all_calls()
//
// Override TurfAdjacent for AltClicking
//
/mob/living/silicon/ai/TurfAdjacent(turf/target_turf)
return (GLOB.cameranet && GLOB.cameranet.checkTurfVis(target_turf))