Skip to content

Commit 4ac8bd1

Browse files
authored
Merge pull request #2 from stinowdev/develop
feat: collectible behavior for brandishing lit torches and update docs
2 parents 0600ca7 + 4ee2b2e commit 4ac8bd1

6 files changed

Lines changed: 238 additions & 292 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ All notable changes to this project will be documented in this file.
44

55
Feature (F) and decision (D) numbers refer to [FEATURES.md](FEATURES.md).
66

7-
## [v0.1.0]
7+
## [Unreleased]
88

99
### Added
1010

1111
- Initial Pyrophobia project scaffold.
1212
- Universal client and server mod entry point.
1313
- F01: hold right-click with no block targeted to brandish a lit main-hand
14-
torch, with third- and first-person raise animations. Block-targeted
15-
right-clicks keep vanilla placement, relighting, and ignition.
14+
torch (collectible behavior), with third- and first-person raise animations.
15+
Block-targeted right-clicks keep vanilla placement, relighting, and ignition.
1616
- Initial design baseline for torch, fire, and wildlife interactions.
1717
- Local build and packaging script.
1818
- GitHub Actions build and draft-release workflow.

FEATURES.md

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -49,23 +49,13 @@ is in the main hand. Scoping the stance to actual threats is F02/F03 work.
4949
| D05 | Planned | A scare probability is evaluated on controlled intervals, not every game tick. |
5050
| D06 | Planned | Fire reactions are configurable per fire source and animal behavior profile. |
5151
| D07 | Active | Brandishing uses the interact (right-click) channel and only begins with no block targeted, because vanilla assigns every block-targeted right-click of a lit torch: plain right-click places it and `CanIgnite` owns the shift ignite gesture. Block-targeted input reaches the mod untouched. |
52+
| D08 | Active | F01 is a `CollectibleBehavior` prepended onto lit torches in `AssetsFinalize` (both sides), not Harmony. Must run before `CanIgnite` and use `PreventSubsequent` while raised so aim-crossing a block cannot start fires. Code attach (not a JSON patch) so we can skip extinct / non-torch `BlockTorch` assets. |
5253

53-
## Design notes
54+
## Extension inventory
5455

55-
Fire is treated as a signal, not a universal fear effect. A torch held up by
56-
the player may be more immediate and threatening than a distant firepit. Some
57-
predatory animals may investigate an isolated fire, creating a configurable
58-
counter-effect rather than a guaranteed safety zone.
59-
60-
The first release should distinguish between:
61-
62-
- fleeing from the player;
63-
- abandoning an active pursuit without fleeing;
64-
- investigating a fire source; and
65-
- remaining unaffected.
66-
67-
## Configuration contract
68-
69-
The scaffold has no runtime configuration yet. Add serialized settings only
56+
| Kind | Type | Side | Feature | Notes |
57+
|---|---|---|---|---|
58+
| CollectibleBehavior | `BrandishTorch` | Universal | F01 | Prepended in `AssetsFinalize` on lit `BlockTorch`. Stance is in-memory, not `Attributes`. |
59+
erialized settings only
7060
when a feature uses them, define ownership and synchronization here, and
7161
document user-facing values in [README.md](README.md).

resources/modinfo.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
"side": "universal",
99
"requiredOnClient": true,
1010
"requiredOnServer": true,
11-
"networkVersion": "1.0.0",
1211
"dependencies": {
1312
"game": "1.22.3"
1413
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
using System;
2+
using System.Collections.Concurrent;
3+
using Vintagestory.API.Common;
4+
using Vintagestory.API.Common.Entities;
5+
using Vintagestory.GameContent;
6+
7+
namespace Pyrophobia.Behaviors;
8+
9+
/// <summary>
10+
/// F01 / D08: brandish a lit main-hand torch (universal, main thread).
11+
/// Prepended ahead of <c>CanIgnite</c>; stance is in-memory, not Attributes.
12+
/// </summary>
13+
public class CollectibleBehaviorBrandishTorch : CollectibleBehavior
14+
{
15+
public const string ClassName = "BrandishTorch";
16+
private const string TorchBrandishAnimation = "pyrophobia-brandishtorch";
17+
18+
// Cleared in ModSystem.Dispose.
19+
private static readonly ConcurrentDictionary<long, byte> BrandishingByEntityId = new();
20+
21+
public CollectibleBehaviorBrandishTorch(CollectibleObject collObj)
22+
: base(collObj)
23+
{
24+
}
25+
26+
internal static void ResetStanceState() => BrandishingByEntityId.Clear();
27+
28+
/// <summary>
29+
/// PreventDefault marks the use as handled so the caller sets
30+
/// <c>Controls.HandUse</c>. With a block targeted or Shift held we
31+
/// PassThrough, which keeps placement, relighting, and CanIgnite vanilla.
32+
/// </summary>
33+
public override void OnHeldInteractStart(
34+
ItemSlot slot,
35+
EntityAgent byEntity,
36+
BlockSelection blockSel,
37+
EntitySelection entitySel,
38+
bool firstEvent,
39+
ref EnumHandHandling handHandling,
40+
ref EnumHandling handling)
41+
{
42+
if (!IsBrandishableTorch(slot, byEntity))
43+
{
44+
return;
45+
}
46+
47+
if (byEntity.Controls.ShiftKey || blockSel != null)
48+
{
49+
SetBrandishing(byEntity, false);
50+
return;
51+
}
52+
53+
SetBrandishing(byEntity, true);
54+
handHandling = EnumHandHandling.PreventDefault;
55+
handling = EnumHandling.PreventDefault;
56+
}
57+
58+
/// <summary>
59+
/// Keep HandUse active for the hold duration, even when aim later crosses
60+
/// a block. PreventSubsequent stops CanIgnite from treating that as ignite.
61+
/// </summary>
62+
public override bool OnHeldInteractStep(
63+
float secondsUsed,
64+
ItemSlot slot,
65+
EntityAgent byEntity,
66+
BlockSelection blockSel,
67+
EntitySelection entitySel,
68+
ref EnumHandling handling)
69+
{
70+
if (!IsBrandishing(byEntity))
71+
{
72+
return false;
73+
}
74+
75+
handling = EnumHandling.PreventSubsequent;
76+
return true;
77+
}
78+
79+
/// <summary>
80+
/// Accept cancellation (release / swap) and ease the stance out.
81+
/// </summary>
82+
public override bool OnHeldInteractCancel(
83+
float secondsUsed,
84+
ItemSlot slot,
85+
EntityAgent byEntity,
86+
BlockSelection blockSel,
87+
EntitySelection entitySel,
88+
EnumItemUseCancelReason cancelReason,
89+
ref EnumHandling handled)
90+
{
91+
if (!IsBrandishing(byEntity))
92+
{
93+
return true;
94+
}
95+
96+
SetBrandishing(byEntity, false);
97+
handled = EnumHandling.PreventSubsequent;
98+
return true;
99+
}
100+
101+
/// <summary>
102+
/// Brandishing produces no effect on stop. PreventSubsequent keeps
103+
/// CanIgnite's stop handler from starting fires after a long hold that
104+
/// began as a brandish and later aimed at a block.
105+
/// </summary>
106+
public override void OnHeldInteractStop(
107+
float secondsUsed,
108+
ItemSlot slot,
109+
EntityAgent byEntity,
110+
BlockSelection blockSel,
111+
EntitySelection entitySel,
112+
ref EnumHandling handling)
113+
{
114+
if (!IsBrandishing(byEntity))
115+
{
116+
return;
117+
}
118+
119+
SetBrandishing(byEntity, false);
120+
handling = EnumHandling.PreventSubsequent;
121+
}
122+
123+
/// <summary>
124+
/// Only active brandishes get the stance animation; vanilla uses such as
125+
/// the ignite gesture keep their own animation.
126+
/// </summary>
127+
public override string GetHeldTpUseAnimation(
128+
ItemSlot activeHotbarSlot,
129+
Entity forEntity,
130+
ref EnumHandling bhHandling)
131+
{
132+
if (forEntity is EntityAgent agent &&
133+
IsBrandishing(agent) &&
134+
IsBrandishableTorch(activeHotbarSlot, forEntity))
135+
{
136+
bhHandling = EnumHandling.PreventDefault;
137+
return TorchBrandishAnimation;
138+
}
139+
140+
return null!;
141+
}
142+
143+
/// <summary>
144+
/// Lit main-hand torch only. Slot identity keeps mirrored calls for other
145+
/// slots or entities on their vanilla path.
146+
/// </summary>
147+
private bool IsBrandishableTorch(ItemSlot slot, Entity byEntity)
148+
{
149+
if (collObj is not BlockTorch torch ||
150+
torch.IsExtinct ||
151+
collObj.Code?.Path.Contains("torch", StringComparison.OrdinalIgnoreCase) != true ||
152+
slot.Itemstack?.Collectible != collObj ||
153+
byEntity is not EntityPlayer entityPlayer ||
154+
!ReferenceEquals(slot, entityPlayer.RightHandItemSlot))
155+
{
156+
return false;
157+
}
158+
159+
return true;
160+
}
161+
162+
private static bool IsBrandishing(EntityAgent byEntity) =>
163+
BrandishingByEntityId.ContainsKey(byEntity.EntityId);
164+
165+
private static void SetBrandishing(EntityAgent byEntity, bool value)
166+
{
167+
if (value)
168+
{
169+
BrandishingByEntityId[byEntity.EntityId] = 0;
170+
}
171+
else
172+
{
173+
BrandishingByEntityId.TryRemove(byEntity.EntityId, out _);
174+
}
175+
}
176+
}

0 commit comments

Comments
 (0)