Skip to content

Commit

Permalink
Makes playing cards playable -- significantly reworks card interactio…
Browse files Browse the repository at this point in the history
…ns (ParadiseSS13#26585)

* Basic card interactions

* initial interactions sorted

* nails down some interactions, radial menu

* Some more qol and keybind changes

* improves card interactions

* Unum decks can be flipped, showing the top card

* Decks can now be split and recombined

* minor tweaks to multi-deck stuff

* Clean up a bunch of interactions

* more cleanups

* more cleanups and documentation

* remote attacking looks pretty good

* minor cleanups

* ci

* parser errs

* Remove some debug things, re-add signal after merge

* Adds a whole bunch more qol.

* Improves examine messages

* ci

* concealed cards will now show properly in radial

* Fixes some weird proximity monitor and control issues

* Address reviews, clean up examine a bit more

* radial fixes

* remove todos

* Apply suggestions from code review

Co-authored-by: Burzah <[email protected]>
Signed-off-by: Luc <[email protected]>

* better documentation, adds label of who did what

* b etter grammar

* augh

* missing icon state

* Update code/game/gamemodes/wizard/magic_tarot.dm

Co-authored-by: Burzah <[email protected]>
Signed-off-by: Luc <[email protected]>

* Apply suggestions from code review

Co-authored-by: DGamerL <[email protected]>
Signed-off-by: Luc <[email protected]>

* Add sleep on proximity monitor setup to hopefully avoid mapload issues

* been writing too much c lately

* Try late initialize

* late init

* 🧌

* map load

---------

Signed-off-by: Luc <[email protected]>
Co-authored-by: Burzah <[email protected]>
Co-authored-by: DGamerL <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent cf31fa0 commit 8da12bc
Show file tree
Hide file tree
Showing 11 changed files with 903 additions and 177 deletions.
2 changes: 2 additions & 0 deletions code/__DEFINES/dcs/atom_signals.dm
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,5 @@
#define COMPONENT_NO_MOUSEDROP (1<<0)
///from base of atom/MouseDrop_T: (/atom/from, /mob/user)
#define COMSIG_MOUSEDROPPED_ONTO "mousedropped_onto"
/// On a ranged attack: base of mob/living/carbon/human/RangedAttack (/mob/living/carbon/human)
#define COMSIG_ATOM_RANGED_ATTACKED "atom_range_attacked"
2 changes: 2 additions & 0 deletions code/__DEFINES/is_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@

#define isstack(I) (istype(I, /obj/item/stack))

#define istable(S) (istype(S, /obj/structure/table))

GLOBAL_LIST_INIT(pointed_types, typecacheof(list(
/obj/item/pen,
/obj/item/screwdriver,
Expand Down
1 change: 1 addition & 0 deletions code/__HELPERS/trait_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NPC_ZOMBIE "npc_zombie" // A trait for checking if a zombie should act like an NPC and attack
#define TRAIT_ABSTRACT_HANDS "abstract_hands" // Mobs with this trait can only pick up abstract items.
#define TRAIT_LANGUAGE_LOCKED "language_locked" // cant add/remove languages until removed (excludes babel because fuck everything i guess)
#define TRAIT_PLAYING_CARDS "playing_cards"
#define TRAIT_EMP_IMMUNE "emp_immune" //The mob will take no damage from EMPs
#define TRAIT_EMP_RESIST "emp_resist" //The mob will take less damage from EMPs
#define TRAIT_MINDFLAYER_NULLIFIED "flayer_nullified" //The mindflayer will not be able to activate their abilities, or drain swarms from people
Expand Down
4 changes: 3 additions & 1 deletion code/_onclick/click.dm
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,11 @@
/mob/proc/RangedAttack(atom/A, params)
if(SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) & COMPONENT_CANCEL_ATTACK_CHAIN)
return TRUE
if(SEND_SIGNAL(A, COMSIG_ATOM_RANGED_ATTACKED, src) & COMPONENT_CANCEL_ATTACK_CHAIN)
return TRUE

/*
Restrained ClickOn
Used when you are handcuffed and click things.
Not currently used by anything but could easily be.
*/
Expand Down
3 changes: 2 additions & 1 deletion code/_onclick/other_mobs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

/mob/living/carbon/human/RangedAttack(atom/A, params)
. = ..()
if(.)
return
if(gloves)
var/obj/item/clothing/gloves/G = gloves
if(istype(G) && G.Touch(A, 0)) // for magic gloves
Expand All @@ -54,7 +56,6 @@

if(isturf(A) && get_dist(src, A) <= 1)
Move_Pulled(A)

/*
Animals & All Unspecified
*/
Expand Down
164 changes: 164 additions & 0 deletions code/datums/components/proximity_monitor.dm
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,167 @@
. = ..()
if(active && AM != monitor.hasprox_receiver && !(AM in monitor.nested_receiver_locs))
monitor.hasprox_receiver.HasProximity(AM)

/// A custom proximity monitor used for tracking players around a table of cards.

/datum/component/proximity_monitor/table
/// How far away you can be (in terms of table squares).
var/max_table_distance
/// How far away you can be (euclidean distance).
var/max_total_distance
/// The UID of the deck
var/deck_uid
/// Whether the monitors created should be visible. Used for debugging.
var/monitors_visible = FALSE

/datum/component/proximity_monitor/table/Initialize(_radius = 1, _always_active = FALSE, _max_table_distance = 5)
max_table_distance = _max_table_distance
max_total_distance = _max_table_distance
. = ..(_radius, _always_active)
if(istype(parent, /obj/item/deck))
// this is important for tracking traits and attacking multiple cards. so it's not a true UID, sue me
var/obj/item/deck/D = parent
deck_uid = D.main_deck_id
else
deck_uid = parent.UID()
addtimer(CALLBACK(src, PROC_REF(refresh)), 0.5 SECONDS)

/datum/component/proximity_monitor/table/proc/refresh()
var/list/tables = list()
var/list/prox_mon_spots = list()
crawl_along(get_turf(parent), tables, prox_mon_spots, 0)
QDEL_LIST_CONTENTS(proximity_checkers)
create_prox_checkers()

/// Crawl along an extended table, and return a list of all turfs that we should start tracking.
/datum/component/proximity_monitor/table/proc/crawl_along(turf/current_turf, list/visited_tables = list(), list/prox_mon_spots = list(), distance_from_start)
var/obj/structure/current_table = locate(/obj/structure/table) in current_turf

if(QDELETED(current_table))
// if there's no table here, we're still adjacent to a table, so this is a spot you could play from
prox_mon_spots |= current_turf
return

if(current_table in visited_tables)
return

visited_tables |= current_table
prox_mon_spots |= current_turf

if(distance_from_start + 1 > max_table_distance)
return

for(var/direction in GLOB.alldirs)
var/turf/next_turf = get_step(current_table, direction)
if(!istype(next_turf))
stack_trace("Failed to proceed in direction [dir2text(direction)] when building card proximity monitors.")
continue
if(get_dist_euclidian(get_turf(parent), next_turf) > max_total_distance)
continue
.(next_turf, visited_tables, prox_mon_spots, distance_from_start + 1)

/datum/component/proximity_monitor/table/create_prox_checkers()
update_prox_checkers(FALSE)

/**
* Update the proximity monitors making up this component.
* Arguments:
* * clear_existing - If true, any existing proximity monitors attached to this will be deleted.
*/
/datum/component/proximity_monitor/table/proc/update_prox_checkers(clear_existing = TRUE)
var/list/tables = list()
var/list/prox_mon_spots = list()
if(length(proximity_checkers))
QDEL_LIST_CONTENTS(proximity_checkers)

var/atom/movable/atom_parent = parent

// if we don't have a parent, just treat it normally
if(!isturf(atom_parent.loc) || !locate(/obj/structure/table) in get_turf(parent))
return


LAZYINITLIST(proximity_checkers)
crawl_along(get_turf(parent), tables, prox_mon_spots, 0)

// For whatever reason their turf is null. Create the checkers in nullspace for now. When the parent moves to a valid turf, they can be recenetered.
for(var/T in prox_mon_spots)
create_single_prox_checker(T, /obj/effect/abstract/proximity_checker/table)

for(var/atom/table in tables)
RegisterSignal(table, COMSIG_PARENT_QDELETING, PROC_REF(on_table_qdel), TRUE)

/datum/component/proximity_monitor/table/on_receiver_move(datum/source, atom/old_loc, dir)
update_prox_checkers()

/datum/component/proximity_monitor/table/RegisterWithParent()
if(ismovable(hasprox_receiver))
RegisterSignal(hasprox_receiver, COMSIG_MOVABLE_MOVED, PROC_REF(on_receiver_move))

/datum/component/proximity_monitor/table/proc/on_table_qdel()
SIGNAL_HANDLER // COMSIG_PARENT_QDELETED
update_prox_checkers()

/obj/effect/abstract/proximity_checker/table
/// The UID for the deck, used in the setting and removal of traits
var/deck_uid

/obj/effect/abstract/proximity_checker/table/Initialize(mapload, datum/component/proximity_monitor/table/P)
. = ..()
deck_uid = P.deck_uid
// catch any mobs on our tile
for(var/mob/living/L in get_turf(src))
register_on_mob(L)

if(P.monitors_visible)
icon = 'icons/obj/playing_cards.dmi'
icon_state = "tarot_the_unknown"
invisibility = INVISIBILITY_MINIMUM
layer = MOB_LAYER

/obj/effect/abstract/proximity_checker/table/Destroy()
var/obj/effect/abstract/proximity_checker/table/same_monitor
for(var/obj/effect/abstract/proximity_checker/table/mon in get_turf(src))
if(mon != src && mon.deck_uid == src)
// if we have another monitor on our space that shares our deck,
// transfer the signals to it.
same_monitor = mon

for(var/mob/living/L in get_turf(src))
remove_from_mob(L)
if(!isnull(same_monitor))
same_monitor.register_on_mob(L)
return ..()

/obj/effect/abstract/proximity_checker/table/proc/register_on_mob(mob/living/L)
ADD_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
RegisterSignal(L, COMSIG_MOVABLE_MOVED, PROC_REF(on_move_from_monitor), TRUE)
RegisterSignal(L, COMSIG_PARENT_QDELETING, PROC_REF(remove_from_mob), TRUE)


/obj/effect/abstract/proximity_checker/table/proc/remove_from_mob(mob/living/L)
if(QDELETED(L))
return
// otherwise, clean up
REMOVE_TRAIT(L, TRAIT_PLAYING_CARDS, "deck_[deck_uid]")
UnregisterSignal(L, COMSIG_MOVABLE_MOVED)

/obj/effect/abstract/proximity_checker/table/Crossed(atom/movable/AM, oldloc)
if(!isliving(AM))
return

var/mob/mover = AM

// This should hopefully ensure that multiple decks around each other don't overlap
register_on_mob(mover)

/// Triggered when someone moves from a tile that contains our monitor.
/obj/effect/abstract/proximity_checker/table/proc/on_move_from_monitor(atom/movable/tracked, atom/old_loc)
SIGNAL_HANDLER // COMSIG_MOVABLE_MOVED
for(var/obj/effect/abstract/proximity_checker/table/mon in get_turf(tracked))
// if we're moving onto a turf that shares our stuff, keep the signals and stuff registered
if(mon.deck_uid == deck_uid)
return

// otherwise, clean up
remove_from_mob(tracked)
28 changes: 23 additions & 5 deletions code/game/gamemodes/wizard/magic_tarot.dm
Original file line number Diff line number Diff line change
Expand Up @@ -234,25 +234,43 @@
/obj/item/magic_tarot_card/proc/pre_activate(mob/user, atom/movable/thrower)
has_been_activated = TRUE
forceMove(user)
var/obj/effect/temp_visual/tarot_preview/draft = new /obj/effect/temp_visual/tarot_preview(user, our_tarot.card_icon)
var/obj/effect/temp_visual/card_preview/tarot/draft = new(user, "tarot_[our_tarot.card_icon]")
user.vis_contents += draft
user.visible_message("<span class='hierophant'>[user] holds up [src]!</span>")
addtimer(CALLBACK(our_tarot, TYPE_PROC_REF(/datum/tarot, activate), user), 0.5 SECONDS)
if(ismob(thrower) && our_tarot)
add_attack_logs(thrower, user, "[thrower] has activated [our_tarot.name] on [user]", ATKLOG_FEW)
QDEL_IN(src, 0.6 SECONDS)

/obj/effect/temp_visual/tarot_preview
name = "a tarot card"
/obj/effect/temp_visual/card_preview
name = "a card"
icon = 'icons/obj/playing_cards.dmi'
icon_state = "tarot_the_unknown"
pixel_y = 20
duration = 1.5 SECONDS

/obj/effect/temp_visual/tarot_preview/Initialize(mapload, new_icon_state)
/obj/effect/temp_visual/card_preview/Initialize(mapload, new_icon_state)
. = ..()
if(new_icon_state)
icon_state = "tarot_[new_icon_state]"
icon_state = new_icon_state

flourish()

/obj/effect/temp_visual/card_preview/proc/flourish()
var/new_filter = isnull(get_filter("ray"))
ray_filter_helper(1, 40, "#fcf3dc", 6, 20)
if(new_filter)
animate(get_filter("ray"), alpha = 0, offset = 10, time = duration, loop = -1)
animate(offset = 0, time = duration)

/obj/effect/temp_visual/card_preview/tarot
name = "a tarot card"
icon = 'icons/obj/playing_cards.dmi'
icon_state = "tarot_the_unknown"
pixel_y = 20
duration = 1.5 SECONDS

/obj/effect/temp_visual/card_preview/tarot/flourish()
var/new_filter = isnull(get_filter("ray"))
ray_filter_helper(1, 40,"#fcf3dc", 6, 20)
if(new_filter)
Expand Down
Loading

0 comments on commit 8da12bc

Please sign in to comment.