Skip to content

Toy process fix#3389

Merged
SmiLeYre merged 12 commits into
BlueMoon-Labs:masterfrom
Kubic70:ToyProcessFix
Jun 30, 2026
Merged

Toy process fix#3389
SmiLeYre merged 12 commits into
BlueMoon-Labs:masterfrom
Kubic70:ToyProcessFix

Conversation

@Kubic70

@Kubic70 Kubic70 commented Jun 29, 2026

Copy link
Copy Markdown

Описание

Много фиксов ЕРП стафа
Игрушки использующие костыльный цикл, перенесены в process().
Логика моба, перенесена в его BiologicalLife().
Исправлено(надеюсь, не тестировано) разрыв чокера при оральных утехах.
Добавлены рамки большим холстам.

Причина изменений

Пожалуйста, если вы не знаете как, что то сделать, смотрите как подобное уже сделано в другом месте.
Конструкция:

/proc/activate_after(obj, delay)
        var/endtime = world.time + delay
        . = 1
        while (world.time < endtime)
	        stoplag()

Еще и в главном цикле игры, это просто смерть производительности.
Для бесконечных лупов, у объектов есть process(), у мобов есть BiologicalLife(), которые имеют какое то подобие асинхронности и приоритетов вызовов.

Changelog

🆑
refactor: refactor vibrators loop code
refactor: refactor dildo loop code
refactor: refactor sybian loop code
refactor: refactor buttplug loop code
refactor: refactor tentacle attack code
refactor: refactor magicwand loop code
refactor: refactor dildomachine loop code
refactor: refactor tentaclepanties loop code
code: added SSobjlw (more faster subsystem for some lewd toys)
fix: fixed large canvas frames
fix: fixed choker snap
/:cl:

Summary by CodeRabbit

  • Новые функции

    • Улучшена анимация и отображение рамок для некоторых размеров полотен.
    • Добавлены более плавные и периодические эффекты для нескольких интерактивных предметов и объектов.
  • Исправления

    • Устранены зависания и «залипание» эффектов после удаления или отключения объектов.
    • Скорректированы звуки, сообщения и визуальные эффекты для разных режимов работы предметов.
    • Поведение некоторых связанных объектов и взаимодействий стало стабильнее и предсказуемее.

@github-actions github-actions Bot added the Code label Jun 29, 2026
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

🧹 Nitpick comments (5)
code/game/objects/structures/artstuff.dm (1)

548-556: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Вынесите оффсеты и frame-ресурс из хардкода.

Здесь зашиты магические числа (9, 4, 1) и знание о двух конкретных подтипах холста. Это быстро разъедется при следующем добавлении ещё одного большого канваса или правке спрайтов. Лучше хранить frame_icon, suffix для icon_state и смещения на самом /obj/item/canvas, а здесь только читать их без istype()-ветки.

♻️ Вариант упрощения
 /obj/item/canvas
+	var/frame_icon
+	var/frame_icon_state_suffix = "frame"
+	var/frame_offset_x = -1
+	var/frame_offset_y = -1

 /obj/item/canvas/thirtysix_twentyfour
+	frame_icon = 'icons/obj/art/artstuff_64x64.dmi'
+	frame_icon_state_suffix = "frame_simple"
+	frame_offset_x = -9
+	frame_offset_y = -4

 /obj/item/canvas/fortyfive_twentyseven
+	frame_icon = 'icons/obj/art/artstuff_64x64.dmi'
+	frame_icon_state_suffix = "frame_simple"
+	frame_offset_x = -9
+	frame_offset_y = -4

 /obj/structure/sign/painting/update_overlays()
 	. = ..()
 	if(!current_canvas?.generated_icon)
 		return

 	var/mutable_appearance/MA = mutable_appearance(current_canvas.generated_icon)
 	MA.pixel_x = current_canvas.framed_offset_x
 	MA.pixel_y = current_canvas.framed_offset_y
 	. += MA
-	if(istype(current_canvas, /obj/item/canvas/thirtysix_twentyfour) || istype(current_canvas, /obj/item/canvas/fortyfive_twentyseven))
-		var/mutable_appearance/frame = mutable_appearance('icons/obj/art/artstuff_64x64.dmi', "[current_canvas.icon_state]frame_simple")
-		frame.pixel_x = current_canvas.framed_offset_x - 9
-		frame.pixel_y = current_canvas.framed_offset_y - 4
-		. += frame
-	else
-		var/mutable_appearance/frame = mutable_appearance(current_canvas.icon,"[current_canvas.icon_state]frame")
-		frame.pixel_x = current_canvas.framed_offset_x - 1
-		frame.pixel_y = current_canvas.framed_offset_y - 1
-		. += frame
+	var/frame_icon = current_canvas.frame_icon || current_canvas.icon
+	var/mutable_appearance/frame = mutable_appearance(frame_icon, "[current_canvas.icon_state][current_canvas.frame_icon_state_suffix]")
+	frame.pixel_x = current_canvas.framed_offset_x + current_canvas.frame_offset_x
+	frame.pixel_y = current_canvas.framed_offset_y + current_canvas.frame_offset_y
+	. += frame

As per path instructions, **/*.dm: "No magic numbers — use #define constants".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/game/objects/structures/artstuff.dm` around lines 548 - 556, The framing
logic in the canvas appearance code is hardcoded with magic offsets and a
special-case type check, making it brittle for new canvas variants. Move the
frame icon resource, icon_state suffix, and pixel offsets onto /obj/item/canvas
(or shared vars used by current_canvas) and have this branch read those values
directly instead of using istype() for specific subtypes. Replace the literal 9,
4, and 1 adjustments with named constants or per-canvas properties, and keep the
mutable_appearance setup in the existing canvas/frame rendering path.

Source: Path instructions

code/controllers/subsystem/processing/obj.dm (1)

8-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Вынесите интервал SSobjlw в именованную константу и time define.

wait = 2 скрывает, что тут используются децисекунды, и это легко перепутать с секундами при настройке delta_time-логики. Лучше оформить это как #define с SECONDS, например 0.2 SECONDS. As per path instructions, use Time defines (SECONDS, MINUTES, HOURS) not raw deciseconds and No magic numbers — use #define constants.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/controllers/subsystem/processing/obj.dm` around lines 8 - 12, The
ObjectsLewd subsystem definition uses a raw decisecond value in
PROCESSING_SUBSYSTEM_DEF(objlw), which should be replaced with a named constant
and time define. Update the wait setting to use a `#define` for the interval and
express it with SECONDS (for example, 0.2 SECONDS) so the timing intent is
explicit and not mistaken for seconds. Keep the change localized to the objlw
subsystem definition and the new constant declaration.

Source: Path instructions

modular_bluemoon/code/modules/arousal/toys/dildos.dm (1)

159-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Не оставляйте интервал процесса сырыми децисекундами.

rand(50,350) лучше заменить на именованные #define-границы через SECONDS, чтобы единицы и баланс были явными.

♻️ Возможная правка
+#define DILDO_PROCESS_MIN_INTERVAL (5 SECONDS)
+#define DILDO_PROCESS_MAX_INTERVAL (35 SECONDS)
+
 /obj/item/dildo/process(delta_time)
 	if(timer > 0) // chech interval
 		timer -= delta_time
 		return
 	else
-		timer = rand(50,350)
+		timer = rand(DILDO_PROCESS_MIN_INTERVAL, DILDO_PROCESS_MAX_INTERVAL)

As per path instructions, "No magic numbers — use #define constants" and "Time defines (SECONDS, MINUTES, HOURS) not raw deciseconds".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/dildos.dm` around lines 159 - 163,
The timer reset in the dildo arousal logic uses raw decisecond values, which
should be replaced with named time constants. Update the timer assignment in the
relevant toy cooldown logic so the interval is defined through existing or new
`#define` constants using SECONDS, rather than rand(50,350), and keep the timer
handling in the same function/block so the balance and units are explicit.

Source: Path instructions

modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm (1)

90-140: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Вынесите интервалы в named time constants.

rand(120,300), rand(5,10), rand(2,5), rand(0.8,2) и rand(0.2,0.5) сейчас выглядят как магические числа. Для processing-таймеров лучше использовать #define с понятными именами и SECONDS.

As per path instructions, "No magic numbers — use #define constants" and "Time defines (SECONDS, MINUTES, HOURS) not raw deciseconds".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm` around lines
90 - 140, The timer interval values in the tentacle_panties processing logic are
hardcoded magic numbers; replace the raw rand() ranges in the tired reset and
lust-based timer branches with named time constants using `#define` and SECONDS.
Update the timing logic in the routine that manages timer, tired, and lust so
the intervals are self-documenting and consistent with the project’s time-define
conventions.

Source: Path instructions

modular_bluemoon/code/modules/arousal/toys/buttplug.dm (1)

137-141: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Замените сырой интервал на именованные time-константы.

rand(50,350) скрывает единицы измерения и нарушает локальный DM-style для таймеров. Лучше вынести границы в #define и записать как 5 SECONDS / 35 SECONDS.

♻️ Возможная правка
+#define BUTTPLUG_PROCESS_MIN_INTERVAL (5 SECONDS)
+#define BUTTPLUG_PROCESS_MAX_INTERVAL (35 SECONDS)
+
 /obj/item/buttplug/process(delta_time)
 	if(timer > 0) // chech interval
 		timer -= delta_time
 		return
 	else
-		timer = rand(50,350)
+		timer = rand(BUTTPLUG_PROCESS_MIN_INTERVAL, BUTTPLUG_PROCESS_MAX_INTERVAL)
 	else
 		to_chat(user, span_love(pick("Я чувствую анальную затычку внутри!", "Вы чувствуете удовольствие от затычки глубоко внутри!")))
 		user.handle_post_sex(LOW_LUST*2, null, user)
 		user.plug13_genital_emote(loc, LOW_LUST*2 * 2)
+
+#undef BUTTPLUG_PROCESS_MIN_INTERVAL
+#undef BUTTPLUG_PROCESS_MAX_INTERVAL

As per path instructions, "No magic numbers — use #define constants" and "Time defines (SECONDS, MINUTES, HOURS) not raw deciseconds".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/buttplug.dm` around lines 137 -
141, The timer reset in the bu

ttplug arousal module uses raw numeric bounds in the `rand(...)` call, which
should be replaced with named time constants. Update the timer logic in
`buttplug.dm` to use `#define`d min/max interval constants and express them with
DM time defines like `5 SECONDS` and `35 SECONDS` so the intent and units are
explicit. Keep the `timer`/`delta_time` flow unchanged, and adjust the
`rand(...)` assignment in the same block to reference those constants instead of
magic numbers.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modular_bluemoon/code/modules/arousal/structure/dildo_machine.dm`:
- Around line 26-28: The Destroy override is too broad because it targets
/obj/structure/bed instead of only the dildo machine. Update the Destroy()
definition in the dildo_machine hierarchy to
/obj/structure/bed/dildo_machine/Destroy() so STOP_PROCESSING(SSobjlw, src) only
runs for that specific object and does not affect all beds. Keep the existing
cleanup flow with the parent call intact.
- Around line 129-142: The timing logic in dildo_machine.process currently
triggers fuck() immediately on the first tick and adds an extra subsystem tick
to every interval because timer is checked before being decremented. Update the
timer handling in the dildo_machine activation/process flow so timer is
initialized when turning on, then in process(delta_time) subtract delta_time
first and only call fuck() once the timer reaches zero or below, resetting it
from speed_delay[mode] after the action. Keep the fix localized to the existing
timer, on state, and process() logic.

In `@modular_bluemoon/code/modules/arousal/structure/sybian.dm`:
- Around line 43-56: The timing logic in sybian.process is causing the first
vibration to fire immediately and then stretching the interval because timer
starts at 0 and is checked before being decremented. Update the start/stop flow
in the on block and process(delta_time) so timer is reset to interval when
activation begins and the countdown subtracts delta_time before deciding whether
to vibrate. Keep the fix localized to /obj/structure/chair/sybian and its
timer/interval handling so re-enabling does not reuse stale timer state.

In `@modular_bluemoon/code/modules/arousal/toys/magic_wand.dm`:
- Around line 192-211: The shared post-use effects in magic_wand.dm are
incorrectly nested under M.has_vagina(), so characters with only a penis miss
handle_post_sex, plug13 emote, jitter, and sound in modes 1 and 2. Move the
common effect block out of the M.has_vagina() branch inside the relevant magic
wand handling logic so it runs for any applicable target, while keeping the
sex-specific chat messages gated by has_penis() and has_vagina().
- Around line 161-165: The `Mob` value is only a `/mob/living/carbon` hint, so
`w_underwear` access in the `magic_wand.dm` check can still hit a non-human at
runtime. In the relevant proc, cast `Mob` to `/mob/living/carbon/human` with
`astype()` before using `M.w_underwear`, and keep the early exit path in place
when the cast fails or the type check does not pass.
- Around line 167-170: The on/off flow in magic_wand.dm leaves no way to resume
processing after `process()` stops it for an unequipped-off item. Update the
toggle path in `attack_self()` and the shutdown logic in `process()` so that
when the wand is turned back on while still worn (`w_underwear`), it calls
`START_PROCESSING(SSobj, src)` again and resumes periodic vibration; keep
`STOP_PROCESSING(SSobj, src)` only for the off state and ensure `Mob` is handled
consistently when restarting.

In `@modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm`:
- Around line 11-13: The tentacle underwear uses different processing subsystems
for registration and cleanup, which can leave a stale entry behind. Make
`equipped()` and the unequip path use the same subsystem as `Destroy()` and the
`STOP_PROCESSING` calls, and ensure
`/obj/item/clothing/underwear/briefs/tentacle/Destroy()` also clears `Mob`
before calling the parent cleanup. Update the related tentacle underwear
handlers so all processing lifecycle paths consistently target the same
subsystem.
- Around line 83-86: The `M = Mob` cast in the `tentacle_panties` logic is only
a compile-time hint, so it can still leave `M` wrong-typed before `w_underwear`
is accessed. Update the guard in this block to use an `astype()` runtime check
before touching `M.w_underwear`, and keep the `STOP_PROCESSING(SSobjlw, src)`
path for non-human values. Locate the fix around the `Mob` to `human` cast in
the tentacle panties process code.

In `@modular_bluemoon/code/modules/mob/living/living.dm`:
- Around line 37-43: The `snaped` check in `living.dm` is comparing the item
instance `C` from `get_item_by_slot()` against a list of type paths, so `C in
snaped` is always false and the snap branch never runs. Update the condition in
this item-handling block to compare `C` by type/path instead of object
membership, using the existing `snaped` data from `facefuck.dm` and the
surrounding `take_damage()`, `dropItemToGround()`, and `visible_message()`
logic.

In
`@modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm`:
- Around line 172-183: Unify the duplicated `BiologicalLife(delta_time,
times_fired)` logic for `/mob/living/simple_animal/hostile/tentacles` into a
single override so the regen/tired handling and the timer-based
`do_lewd_action()` flow live together in one place. Keep the existing behavior
from both versions inside that one method, and add a null check around `target`
before assigning to `M` or reading `M.pulledby` to avoid runtime errors when no
target is set.
- Around line 180-183: Guard the target before dereferencing it in the tentacles
logic: the current check in the mob routine that assigns target to M and then
reads M.pulledby can runtime when target is null. Update the early flow in the
same tentacles behavior so it returns immediately if M is null, if M.pulledby is
absent, or if tired is true, before calling do_lewd_action.

In `@modular_splurt/code/game/objects/items/lewd_items/vibrator.dm`:
- Around line 126-140: In attack_self() on vibrator.dm, changing mode only
updates the vibration strength but leaves the currently running soundloop on the
old setting until remote-toggle runs. Update the mode-switch logic so the active
soundloop is refreshed immediately whenever the mode changes, using the existing
vibrator/state helpers around attack_self(), startVibing(), and stopVibing() to
keep sound and effects in sync.

---

Nitpick comments:
In `@code/controllers/subsystem/processing/obj.dm`:
- Around line 8-12: The ObjectsLewd subsystem definition uses a raw decisecond
value in PROCESSING_SUBSYSTEM_DEF(objlw), which should be replaced with a named
constant and time define. Update the wait setting to use a `#define` for the
interval and express it with SECONDS (for example, 0.2 SECONDS) so the timing
intent is explicit and not mistaken for seconds. Keep the change localized to
the objlw subsystem definition and the new constant declaration.

In `@code/game/objects/structures/artstuff.dm`:
- Around line 548-556: The framing logic in the canvas appearance code is
hardcoded with magic offsets and a special-case type check, making it brittle
for new canvas variants. Move the frame icon resource, icon_state suffix, and
pixel offsets onto /obj/item/canvas (or shared vars used by current_canvas) and
have this branch read those values directly instead of using istype() for
specific subtypes. Replace the literal 9, 4, and 1 adjustments with named
constants or per-canvas properties, and keep the mutable_appearance setup in the
existing canvas/frame rendering path.

In `@modular_bluemoon/code/modules/arousal/toys/buttplug.dm`:
- Around line 137-141: The timer reset in the bu

ttplug arousal module uses raw numeric bounds in the `rand(...)` call, which
should be replaced with named time constants. Update the timer logic in
`buttplug.dm` to use `#define`d min/max interval constants and express them with
DM time defines like `5 SECONDS` and `35 SECONDS` so the intent and units are
explicit. Keep the `timer`/`delta_time` flow unchanged, and adjust the
`rand(...)` assignment in the same block to reference those constants instead of
magic numbers.

In `@modular_bluemoon/code/modules/arousal/toys/dildos.dm`:
- Around line 159-163: The timer reset in the dildo arousal logic uses raw
decisecond values, which should be replaced with named time constants. Update
the timer assignment in the relevant toy cooldown logic so the interval is
defined through existing or new `#define` constants using SECONDS, rather than
rand(50,350), and keep the timer handling in the same function/block so the
balance and units are explicit.

In `@modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm`:
- Around line 90-140: The timer interval values in the tentacle_panties
processing logic are hardcoded magic numbers; replace the raw rand() ranges in
the tired reset and lust-based timer branches with named time constants using
`#define` and SECONDS. Update the timing logic in the routine that manages timer,
tired, and lust so the intervals are self-documenting and consistent with the
project’s time-define conventions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3173a1b8-c8c5-4c9d-af30-ae3c50498595

📥 Commits

Reviewing files that changed from the base of the PR and between c5ee743 and 8d08e44.

📒 Files selected for processing (12)
  • code/controllers/subsystem/processing/obj.dm
  • code/game/objects/structures/artstuff.dm
  • modular_bluemoon/code/modules/arousal/structure/dildo_machine.dm
  • modular_bluemoon/code/modules/arousal/structure/sybian.dm
  • modular_bluemoon/code/modules/arousal/toys/buttplug.dm
  • modular_bluemoon/code/modules/arousal/toys/dildos.dm
  • modular_bluemoon/code/modules/arousal/toys/magic_wand.dm
  • modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm
  • modular_bluemoon/code/modules/mob/living/living.dm
  • modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
  • modular_sand/code/datums/interactions/interaction_datums/lewd/facefuck.dm
  • modular_splurt/code/game/objects/items/lewd_items/vibrator.dm

Comment on lines +26 to +28
/obj/structure/bed/Destroy()
STOP_PROCESSING(SSobjlw,src)
. = ..()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Destroy() сейчас переопределяет все кровати, а не только dildo_machine.

На Line 26 объявлен /obj/structure/bed/Destroy(), поэтому STOP_PROCESSING(SSobjlw, src) начнёт выполняться для любого /obj/structure/bed. Это слишком широкий хук; вдобавок STOP_PROCESSING снимает общий DF_ISPROCESSING флаг, так что вы трогаете состояние объектов вне этой машины. Здесь нужен /obj/structure/bed/dildo_machine/Destroy().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/structure/dildo_machine.dm` around
lines 26 - 28, The Destroy override is too broad because it targets
/obj/structure/bed instead of only the dildo machine. Update the Destroy()
definition in the dildo_machine hierarchy to
/obj/structure/bed/dildo_machine/Destroy() so STOP_PROCESSING(SSobjlw, src) only
runs for that specific object and does not affect all beds. Keep the existing
cleanup flow with the parent call intact.

Comment on lines +129 to +142
START_PROCESSING(SSobjlw,src)
if(!on)
STOP_PROCESSING(SSobjlw,src)
if(user)
to_chat(user, span_notice("[src] в[on ? "" : "ы"]ключена."))

/obj/structure/bed/dildo_machine/process(delta_time)
if(timer > 0) // chech interval
timer -= delta_time
return
else
timer = speed_delay[mode]

fuck()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Таймер сейчас даёт лишний тик задержки и первый толчок сразу после включения.

С timer = 0 первый process() сразу вызывает fuck(), а проверка if(timer > 0) до вычитания delta_time растягивает каждый следующий интервал ещё на один тик подсистемы. На high это фактически удваивает паузу между вызовами. Инициализируйте timer при включении и сначала уменьшайте его на delta_time, а уже потом проверяйте порог.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/structure/dildo_machine.dm` around
lines 129 - 142, The timing logic in dildo_machine.process currently triggers
fuck() immediately on the first tick and adds an extra subsystem tick to every
interval because timer is checked before being decremented. Update the timer
handling in the dildo_machine activation/process flow so timer is initialized
when turning on, then in process(delta_time) subtract delta_time first and only
call fuck() once the timer reaches zero or below, resetting it from
speed_delay[mode] after the action. Keep the fix localized to the existing
timer, on state, and process() logic.

Comment on lines 43 to +56
if(on)
START_PROCESSING(SSobjlw,src)
to_chat(usr, "[src] вкл.")
else
STOP_PROCESSING(SSobjlw,src)
to_chat(usr, "[src] выкл.")

/obj/structure/chair/sybian/process(delta_time)
if(timer > 0) // chech interval
timer -= delta_time
return
else
timer = interval
vibrate()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Тут тот же сдвиг таймера: первый импульс мгновенный, дальше интервал длиннее заданного.

timer стартует с 0, поэтому после включения vibrate() срабатывает в первый же process(). Затем из-за проверки до timer -= delta_time цикл получает лишний тик ожидания на каждом проходе и ещё может переиспользовать старый timer после повторного включения. Сбрасывайте timer = interval при старте и сначала уменьшайте его на delta_time.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/structure/sybian.dm` around lines 43 -
56, The timing logic in sybian.process is causing the first vibration to fire
immediately and then stretching the interval because timer starts at 0 and is
checked before being decremented. Update the start/stop flow in the on block and
process(delta_time) so timer is reset to interval when activation begins and the
countdown subtracts delta_time before deciding whether to vibrate. Keep the fix
localized to /obj/structure/chair/sybian and its timer/interval handling so
re-enabling does not reuse stale timer state.

Comment on lines 161 to 165
var/mob/living/carbon/human/M = Mob
if(!istype(src, M.w_underwear))
STOP_PROCESSING(SSobj,src)
Mob = null
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Скастуйте Mob перед доступом к human-only полям.

Mob хранится как /mob/living/carbon, но w_underwear есть не у любого carbon-типа. Используйте astype() и ранний выход перед обращением к M.w_underwear.

🛡️ Возможная правка
-	var/mob/living/carbon/human/M = Mob
-	if(!istype(src, M.w_underwear))
+	var/mob/living/carbon/human/M = astype(Mob, /mob/living/carbon/human)
+	if(!M || !istype(src, M.w_underwear))
 		STOP_PROCESSING(SSobj,src)
 		Mob = null
 		return

Based on learnings, DM proc/type annotations are compile-time hints only and are not enforced at runtime. As per path instructions, BYOND 516 code should prefer astype(val, /type).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var/mob/living/carbon/human/M = Mob
if(!istype(src, M.w_underwear))
STOP_PROCESSING(SSobj,src)
Mob = null
return
var/mob/living/carbon/human/M = astype(Mob, /mob/living/carbon/human)
if(!M || !istype(src, M.w_underwear))
STOP_PROCESSING(SSobj,src)
Mob = null
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/magic_wand.dm` around lines 161 -
165, The `Mob` value is only a `/mob/living/carbon` hint, so `w_underwear`
access in the `magic_wand.dm` check can still hit a non-human at runtime. In the
relevant proc, cast `Mob` to `/mob/living/carbon/human` with `astype()` before
using `M.w_underwear`, and keep the early exit path in place when the cast fails
or the type check does not pass.

Sources: Path instructions, Learnings

Comment on lines 167 to 170
if(!on)
STOP_PROCESSING(SSobj,src)
Mob = null
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Не останавливайте processing без пути повторного запуска.

Если предмет надет выключенным, первый process() очищает Mob и снимает объект с SSobj; последующее включение через attack_self() не вызывает START_PROCESSING, поэтому периодическая вибрация уже не запустится до переэкипировки.

🐛 Возможная правка
 	if(!on)
-		STOP_PROCESSING(SSobj,src)
-		Mob = null
 		return

Либо добавьте в attack_self() запуск processing при включении, если src всё ещё находится в w_underwear.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(!on)
STOP_PROCESSING(SSobj,src)
Mob = null
return
if(!on)
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/magic_wand.dm` around lines 167 -
170, The on/off flow in magic_wand.dm leaves no way to resume processing after
`process()` stops it for an unequipped-off item. Update the toggle path in
`attack_self()` and the shutdown logic in `process()` so that when the wand is
turned back on while still worn (`w_underwear`), it calls
`START_PROCESSING(SSobj, src)` again and resumes periodic vibration; keep
`STOP_PROCESSING(SSobj, src)` only for the off state and ensure `Mob` is handled
consistently when restarting.

Comment on lines +83 to +86
var/mob/living/carbon/human/M = Mob

if(!istype(src, M.w_underwear))
STOP_PROCESSING(SSobjlw,src)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Проверьте human-cast перед w_underwear.

Mob объявлен как /mob/living/carbon, поэтому прямое присваивание в human-typed переменную не защищает от wrong-typed значения в DM. Перед доступом к w_underwear нужен astype() guard.

🛡️ Возможная правка
-	var/mob/living/carbon/human/M = Mob
+	var/mob/living/carbon/human/M = astype(Mob, /mob/living/carbon/human)
 
-	if(!istype(src, M.w_underwear))
+	if(!M || !istype(src, M.w_underwear))
 		STOP_PROCESSING(SSobjlw,src)
 		Mob = null
 		return

Based on learnings, DM proc/type annotations are compile-time hints only and are not enforced at runtime. As per path instructions, BYOND 516 code should prefer astype(val, /type).

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var/mob/living/carbon/human/M = Mob
if(!istype(src, M.w_underwear))
STOP_PROCESSING(SSobjlw,src)
var/mob/living/carbon/human/M = astype(Mob, /mob/living/carbon/human)
if(!M || !istype(src, M.w_underwear))
STOP_PROCESSING(SSobjlw,src)
Mob = null
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/arousal/toys/tentacle_panties.dm` around lines
83 - 86, The `M = Mob` cast in the `tentacle_panties` logic is only a
compile-time hint, so it can still leave `M` wrong-typed before `w_underwear` is
accessed. Update the guard in this block to use an `astype()` runtime check
before touching `M.w_underwear`, and keep the `STOP_PROCESSING(SSobjlw, src)`
path for non-human values. Locate the fix around the `Mob` to `human` cast in
the tentacle panties process code.

Sources: Path instructions, Learnings

Comment on lines +37 to +43
if(C in snaped)
if(prob(15))
C.take_damage(80,BRUTE)
M.dropItemToGround(C)
playsound(src, 'modular_bluemoon/sound/effects/snap.ogg', 30, 1, -1)
visible_message(span_danger("[C] snaps!"))
return TRUE

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Проверка snaped сейчас делает ветку недостижимой.

C здесь — экземпляр предмета из get_item_by_slot(), а snaped в facefuck.dm хранит type paths. Условие C in snaped поэтому всегда ложно, и take_damage(), dropItemToGround() и сам “snap” для этих ошейников больше не сработают. Нужна проверка по типу, а не membership по объекту.

Возможная правка
-		if(C in snaped)
+		if(is_type_in_list(C, snaped))
 			if(prob(15))
 				C.take_damage(80,BRUTE)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(C in snaped)
if(prob(15))
C.take_damage(80,BRUTE)
M.dropItemToGround(C)
playsound(src, 'modular_bluemoon/sound/effects/snap.ogg', 30, 1, -1)
visible_message(span_danger("[C] snaps!"))
return TRUE
if(is_type_in_list(C, snaped))
if(prob(15))
C.take_damage(80,BRUTE)
M.dropItemToGround(C)
playsound(src, 'modular_bluemoon/sound/effects/snap.ogg', 30, 1, -1)
visible_message(span_danger("[C] snaps!"))
return TRUE
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_bluemoon/code/modules/mob/living/living.dm` around lines 37 - 43, The
`snaped` check in `living.dm` is comparing the item instance `C` from
`get_item_by_slot()` against a list of type paths, so `C in snaped` is always
false and the snap branch never runs. Update the condition in this item-handling
block to compare `C` by type/path instead of object membership, using the
existing `snaped` data from `facefuck.dm` and the surrounding `take_damage()`,
`dropItemToGround()`, and `visible_message()` logic.

Comment on lines +172 to +183
/mob/living/simple_animal/hostile/tentacles/BiologicalLife(delta_time, times_fired)
. = ..()
if(timer > 0) // chech interval
timer -= delta_time
return
else
timer = rand(5,20)

var/mob/living/M = target

if(M.pulledby && !tired)
do_lewd_action(M)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and inspect the relevant proc definitions.
git ls-files modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
wc -l modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
printf '\n--- first BiologicalLife definition ---\n'
sed -n '80,120p' modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
printf '\n--- second BiologicalLife definition ---\n'
sed -n '160,210p' modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
printf '\n--- search for BiologicalLife occurrences ---\n'
rg -n "BiologicalLife" modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 2952


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the parent proc chain and any target usage to judge whether the concern is real.
rg -n "proc/BiologicalLife|BiologicalLife\(" modular_bluemoon/code/modules/mob/living/simple_animal -g '*.dm'
printf '\n--- tentacles file target usages ---\n'
rg -n "\btarget\b" modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 753


Свести BiologicalLife() в одно место.
Сейчас у /mob/living/simple_animal/hostile/tentacles два BiologicalLife(delta_time, times_fired): первая версия держит реген и спад tired, а вторая добавляет таймер и do_lewd_action(). Это нужно объединить в один override, иначе логика тентаклей распадается на две несвязанные ветки.
target здесь тоже читается без null-guard, так что M.pulledby может давать runtime на тике без цели.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm`
around lines 172 - 183, Unify the duplicated `BiologicalLife(delta_time,
times_fired)` logic for `/mob/living/simple_animal/hostile/tentacles` into a
single override so the regen/tired handling and the timer-based
`do_lewd_action()` flow live together in one place. Keep the existing behavior
from both versions inside that one method, and add a null check around `target`
before assigning to `M` or reading `M.pulledby` to avoid runtime errors when no
target is set.

Comment on lines +180 to +183
var/mob/living/M = target

if(M.pulledby && !tired)
do_lewd_action(M)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file around the cited lines and the BiologicalLife() implementation.
file="modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm"

wc -l "$file"
sed -n '140,230p' "$file"

printf '\n--- search for BiologicalLife / target assignments ---\n'
rg -n "BiologicalLife|var/mob/living/M = target|target =" "$file"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 2712


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm"

printf '--- declarations and target writes ---\n'
rg -n "var/mob/living/target|target =|target\\b" "$file"

printf '\n--- surrounding class definitions ---\n'
sed -n '1,120p' "$file"

printf '\n--- surrounding BiologicalLife override ---\n'
sed -n '160,190p' "$file"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 259


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm"

echo '--- file start ---'
sed -n '1,130p' "$file"

echo
echo '--- BiologicalLife around cited lines ---'
sed -n '160,190p' "$file"

echo
echo '--- target references in file ---'
rg -n "target|BiologicalLife" "$file"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 6375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the base BiologicalLife implementation and any call sites/expectations around target lifetime.
rg -n "/BiologicalLife\\(|proc/BiologicalLife\\(|BiologicalLife\\(delta_time" .

# Also inspect the simple_animal hierarchy for target handling.
rg -n "var/.*target|LoseTarget\\(|CanAttack\\(" modular_bluemoon code modules . -g '*.dm'

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 50387


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Narrow search to likely base mob/simple_animal files if present.
fd -a "simple_animal*.dm" .
fd -a "*living*.dm" .

# Look for the parent BiologicalLife definition and related target lifecycle methods.
rg -n "/mob/.*/BiologicalLife|BiologicalLife\\(delta_time, times_fired\\)|LoseTarget\\(" .

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 767


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the parent implementation and target lifecycle in the simple_animal hierarchy.
rg -n "/mob/living/simple_animal/.*/BiologicalLife|/mob/living/simple_animal/BiologicalLife|LoseTarget\\(|target = null|var/mob/living/target" modular_bluemoon code -g '*.dm'

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 35079


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the hostile parent BiologicalLife and the two tentacles overrides.
sed -n '90,130p' code/modules/mob/living/simple_animal/hostile/hostile.dm
echo
sed -n '80,115p' modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm
echo
sed -n '168,188p' modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 3308


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect target lifecycle in the hostile base.
sed -n '430,455p' code/modules/mob/living/simple_animal/hostile/hostile.dm
echo
sed -n '70,110p' code/modules/mob/living/simple_animal/hostile/hostile.dm

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 2062


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether the tentacles file has duplicate BiologicalLife definitions and how the earlier one differs.
rg -n "^/mob/living/simple_animal/hostile/tentacles/BiologicalLife|^/mob/living/simple_animal/hostile/tentacles/" modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 1280


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the base simple_animal BiologicalLife implementation and timer cadence.
sed -n '1,220p' code/modules/mob/living/simple_animal/simple_animal.dm | rg -n "BiologicalLife|timer|life"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="code/modules/mob/living/simple_animal/simple_animal.dm"
wc -l "$file"
rg -n "/mob/living/simple_animal/BiologicalLife|BiologicalLife\\(" "$file"
sed -n '1,220p' "$file" | sed -n '1,220p'

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="code/modules/mob/living/simple_animal/simple_animal.dm"
wc -l "$file"
rg -n "/mob/living/simple_animal/BiologicalLife|BiologicalLife\\(" "$file"
sed -n '1,220p' "$file"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 227


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm"

# Show the full second BiologicalLife block with a bit more context.
sed -n '168,190p' "$file"

# Show nearby target-setting / target-clearing logic in the same file.
rg -n "target =|LoseTarget\\(|CanAttack\\(|MoveToTarget\\(|BiologicalLife\\(" "$file"

Repository: BlueMoon-Labs/BlueMoon-Station

Length of output: 1039


Нужен guard перед M.pulledby.

target здесь может быть null, и тогда этот тик начнёт спамить runtime при каждом срабатывании таймера. Достаточно раннего if(!M || !M.pulledby || tired) return.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@modular_bluemoon/code/modules/mob/living/simple_animal/tentacles/tentacles.dm`
around lines 180 - 183, Guard the target before dereferencing it in the
tentacles logic: the current check in the mob routine that assigns target to M
and then reads M.pulledby can runtime when target is null. Update the early flow
in the same tentacles behavior so it returns immediately if M is null, if
M.pulledby is absent, or if tired is true, before calling do_lewd_action.

Source: Learnings

Comment on lines +126 to +140
if(VIB_OFF)
mode = VIB_LOW
to_chat(user, span_notice("You twist the bottom of [src], setting it to the low setting."))
return
if(VIB_LOW)
mode = VIB_MEDIUM
to_chat(user, span_notice("You twist the bottom of [src], setting it to the medium setting."))
return
if(2)
mode = 3
if(VIB_MEDIUM)
mode = VIB_HIGH
to_chat(user, span_warning("You twist the bottom of [src], setting it to the high setting."))
return
if(3)
mode = 1
to_chat(user, span_notice("You twist the bottom of [src], setting it to the low setting."))
if(VIB_HIGH)
mode = VIB_OFF
stopVibing()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Обновляйте активный soundloop при смене режима.

Если постоянная вибрация уже запущена, attack_self() меняет mode, но текущий soundloop остаётся прежним до следующего remote-toggle. Эффекты будут считаться по новому режиму, а звук — по старому.

🐛 Возможная правка
 	if(isliving(user))
 		playsound(user, 'sound/effects/clock_tick.ogg', 50, 1, -1)
+		var/was_processing = datum_flags & DF_ISPROCESSING
 		switch(mode)
 			if(VIB_OFF)
 				mode = VIB_LOW
 				to_chat(user, span_notice("You twist the bottom of [src], setting it to the low setting."))
-				return
 			if(VIB_LOW)
 				mode = VIB_MEDIUM
 				to_chat(user, span_notice("You twist the bottom of [src], setting it to the medium setting."))
-				return
 			if(VIB_MEDIUM)
 				mode = VIB_HIGH
 				to_chat(user, span_warning("You twist the bottom of [src], setting it to the high setting."))
-				return
 			if(VIB_HIGH)
 				mode = VIB_OFF
 				stopVibing()
 				to_chat(user, span_notice("You twist the bottom of [src], setting it to the off."))
 				return
+		if(was_processing)
+			startVibing()
+		return

Also applies to: 249-268

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modular_splurt/code/game/objects/items/lewd_items/vibrator.dm` around lines
126 - 140, In attack_self() on vibrator.dm, changing mode only updates the
vibration strength but leaves the currently running soundloop on the old setting
until remote-toggle runs. Update the mode-switch logic so the active soundloop
is refreshed immediately whenever the mode changes, using the existing
vibrator/state helpers around attack_self(), startVibing(), and stopVibing() to
keep sound and effects in sync.

@SmiLeYre SmiLeYre merged commit 4bc80cb into BlueMoon-Labs:master Jun 30, 2026
26 checks passed
github-actions Bot pushed a commit that referenced this pull request Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants