-
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathFollowMe.cs
291 lines (240 loc) · 10.5 KB
/
FollowMe.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Karel Kroeze
// FollowMe.cs
// 2016-12-27
using System;
using System.Linq;
using System.Reflection;
using RimWorld;
using RimWorld.Planet;
using UnityEngine;
using Verse;
using Verse.Sound;
namespace FollowMe {
public class FollowMe: GameComponent {
private static readonly FieldInfo _cameraDriverRootPosField = typeof( CameraDriver ).GetField( "rootPos",
BindingFlags
.Instance |
BindingFlags
.NonPublic );
private static readonly FieldInfo _cameraDriverDesiredDollyField =
typeof( CameraDriver ).GetField( "desiredDolly", BindingFlags.Instance | BindingFlags.NonPublic );
private static bool _cameraHasJumpedAtLeastOnce;
private static bool _currentlyFollowing;
private static bool _enabled = true;
private static Thing _followedThing;
private readonly KeyBindingDef[] _followBreakingKeyBindingDefs =
{
KeyBindingDefOf.MapDolly_Down,
KeyBindingDefOf.MapDolly_Up,
KeyBindingDefOf.MapDolly_Right,
KeyBindingDefOf.MapDolly_Left
};
public FollowMe() {
// scribe
}
public FollowMe(Game game) {
// game init
}
public static string FollowedLabel {
get {
if (_followedThing == null) {
return string.Empty;
}
Pawn pawn = _followedThing as Pawn;
if (pawn?.Name != null) {
return pawn.Name.ToStringShort;
}
return _followedThing.LabelCap;
}
}
private static Vector3 CameraRootPosition {
get {
if (_cameraDriverRootPosField == null) {
throw new NullReferenceException("CameraDriver.rootPos field info NULL");
}
return (Vector3) _cameraDriverRootPosField.GetValue(Find.CameraDriver);
}
}
private static Vector2 CameraDesiredDolly {
get {
if (_cameraDriverDesiredDollyField == null) {
throw new NullReferenceException("CameraDriver.desiredDolly field info NULL");
}
return (Vector2) _cameraDriverDesiredDollyField.GetValue(Find.CameraDriver);
}
}
private static bool MouseOverUI => Find.WindowStack.GetWindowAt(UI.MousePositionOnUIInverted) != null;
public static void TryStartFollow(Thing thing) {
_enabled = true;
if (!_currentlyFollowing && thing == null) {
if (Find.Selector.NumSelected > 1) {
Mod.DoMessage("FollowMe.RejectMultiple".Translate(), MessageTypeDefOf.RejectInput);
} else if (Find.Selector.NumSelected == 0) {
Mod.DoMessage("FollowMe.RejectNoSelection".Translate(), MessageTypeDefOf.RejectInput);
} else {
Mod.DoMessage("FollowMe.RejectNotAThing".Translate(), MessageTypeDefOf.RejectInput);
}
}
// cancel current follow (toggle or thing == null)
else if (_currentlyFollowing && (thing == null || thing == _followedThing)) {
StopFollow("toggled");
}
// follow new thing
else if (thing != null) {
StartFollow(thing);
}
}
private static void StartFollow(Thing thing) {
_followedThing = thing ?? throw new ArgumentNullException(nameof(thing));
_currentlyFollowing = true;
Mod.DoMessage("FollowMe.Follow".Translate(FollowedLabel), MessageTypeDefOf.PositiveEvent);
}
public static void StopFollow(string reason = null) {
#if DEBUG
Log.Message($"FollowMe :: Stopped following {FollowedLabel} :: {reason ?? "NONE" }");
#endif
if (!reason.NullOrEmpty() && _currentlyFollowing) {
Mod.DoMessage("FollowMe.Cancel".Translate(FollowedLabel), MessageTypeDefOf.SituationResolved);
}
_followedThing = null;
_currentlyFollowing = false;
_cameraHasJumpedAtLeastOnce = false;
CinematicCameraManager.Stop(null, false);
}
public override void GameComponentOnGUI() {
if (Current.ProgramState != ProgramState.Playing) {
return; // gamecomp is already active in the 'setup' stage, but follow me shouldnt be.
}
// start/stop following thing on key press
if (Settings.FollowMeKey.JustPressed) {
TryStartFollow(Find.Selector.SingleSelectedObject as Thing);
}
if (Event.current.type == EventType.MouseUp &&
Event.current.button == 2) {
// Get entry at mouse position - UI.MousePositionOnUIInverted handles;
// - inverting y axis (UI starts top right, screen starts bottom right)
// - UI scale
Thing thing = Find.ColonistBar.ColonistOrCorpseAt( UI.MousePositionOnUIInverted );
if (thing != null) {
// start following
TryStartFollow(thing);
// use event so it doesn't bubble through
Event.current.Use();
}
}
}
public override void GameComponentUpdate() {
if (!_enabled) {
return;
}
try {
if (_currentlyFollowing) {
CheckKeyScroll();
CheckCameraJump();
CheckDolly();
if (Settings.edgeDetection) {
CheckScreenEdgeScroll();
}
}
// move camera
Follow();
}
// catch exception to avoid error spam
catch (Exception e) {
_enabled = false;
Log.Error(e.ToString());
}
}
private static void Follow() {
if (!_currentlyFollowing || _followedThing == null) {
return;
}
TryJumpSmooth(_followedThing);
}
public static void TryJumpSmooth(GlobalTargetInfo target) {
target = CameraJumper.GetAdjustedTarget(target);
if (!target.IsValid) {
StopFollow("invalid target");
return;
}
// we have to use our own logic for following spawned things, as CameraJumper
// uses integer positions - which would be jerky.
if (target.HasThing) {
TryJumpSmoothInternal(target.Thing);
}
// However, if we don't have a thing to follow, integer positions will do just fine.
else {
CameraJumper.TryJump(target);
}
_cameraHasJumpedAtLeastOnce = true;
}
private static void TryJumpSmoothInternal(Thing thing) {
// copypasta from Verse.CameraJumper.TryJumpInternal( Thing ),
// but with drawPos instead of PositionHeld.
if (Current.ProgramState != ProgramState.Playing) {
return;
}
Map mapHeld = thing.MapHeld;
if (mapHeld != null && thing.PositionHeld.IsValid && thing.PositionHeld.InBounds(mapHeld)) {
bool flag = CameraJumper.TryHideWorld();
if (Find.CurrentMap != mapHeld) {
Current.Game.CurrentMap = mapHeld;
if (!flag) {
SoundDefOf.MapSelected.PlayOneShotOnCamera();
}
}
Find.CameraDriver.JumpToCurrentMapLoc(thing.DrawPos); // <---
} else {
StopFollow("invalid thing position");
}
}
private static void CheckDolly() {
if (CameraDesiredDolly != Vector2.zero) {
StopFollow("dolly");
}
}
private void CheckKeyScroll() {
if (_followBreakingKeyBindingDefs.Any(key => key.IsDown)) {
StopFollow("moved map (key)");
}
}
private void CheckCameraJump() {
// to avoid cancelling the following immediately after it starts, allow the camera to move to the followed thing once
// before starting to compare positions
if (_cameraHasJumpedAtLeastOnce) {
// the actual location of the camera right now
IntVec3 currentCameraPosition = Find.CameraDriver.MapPosition;
// the location the camera has been requested to be at
IntVec3 requestedCameraPosition = CameraRootPosition.ToIntVec3();
// these normally stay in sync while following is active, since we were the last to request where the camera should go.
// If they get out of sync, it's because the camera has been asked to jump to somewhere else, and we should stop
// following our thing.
if ((currentCameraPosition - requestedCameraPosition).LengthHorizontal > 1) {
StopFollow("map moved (camera jump)");
}
}
}
private void CheckScreenEdgeScroll() {
if (!Application.isFocused || !Prefs.EdgeScreenScroll || MouseOverUI) {
return;
}
Vector3 mousePosition = Input.mousePosition;
Rect[] screenCorners = new[]
{
new Rect( 0f, 0f, 200f, 200f ),
new Rect( Screen.width - 250, 0f, 255f, 255f ),
new Rect( 0f, Screen.height - 250, 225f, 255f ),
new Rect( Screen.width - 250, Screen.height - 250, 255f, 255f )
};
if (screenCorners.Any(e => e.Contains(mousePosition))) {
return;
}
if (mousePosition.x < 20f
|| mousePosition.x > Screen.width - 20
|| mousePosition.y > Screen.height - 20f
|| mousePosition.y < (Screen.fullScreen ? 6f : 20f)) {
StopFollow("moved map (mouse edge)");
}
}
}
}