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
|
#include <libultraship/bridge/consolevariablebridge.h>
#include "2s2h/GameInteractor/GameInteractor.h"
#include "2s2h/Enhancements/FrameInterpolation/FrameInterpolation.h"
#include "2s2h/ShipInit.hpp"
extern "C" {
#include "variables.h"
#include "overlays/actors/ovl_En_Slime/z_en_slime.h"
#include "overlays/actors/ovl_En_Tanron5/z_en_tanron5.h"
void EnItem00_Draw(Actor* thisx, PlayState* play);
void EnTanron5_ItemDrop_Draw(Actor* thisx, PlayState* play);
}
#define CVAR_NAME "gEnhancements.Graphics.3DItemDrops"
#define CVAR CVarGetInteger(CVAR_NAME, 0)
bool ItemShouldSpinWhen3D(Actor* actor) {
EnItem00* enItem00 = (EnItem00*)actor;
// Exclude actors that already have spin normally, or shouldn't spin ever
if ((actor->params <= ITEM00_RUPEE_RED) || ((actor->params == ITEM00_RECOVERY_HEART) && (enItem00->unk152 < 0)) ||
(actor->params == ITEM00_HEART_PIECE) || (actor->params == ITEM00_HEART_CONTAINER) ||
(actor->params == ITEM00_RUPEE_HUGE) || (actor->params == ITEM00_RUPEE_PURPLE) ||
(actor->params == ITEM00_SHIELD_HERO) || (actor->params == ITEM00_MAP) || (actor->params == ITEM00_COMPASS)) {
return false;
}
return true;
}
void EnItem00_3DItemsDraw(Actor* actor, PlayState* play) {
EnItem00* enItem00 = (EnItem00*)actor;
if (!(enItem00->unk14E & enItem00->unk150)) {
Player* player = GET_PLAYER(play);
bool itemOnPlayer = player->actor.home.pos.x == enItem00->actor.world.pos.x &&
player->actor.home.pos.z == enItem00->actor.world.pos.z;
FrameInterpolation_RecordOpenChild(enItem00, itemOnPlayer ? 1 : 0);
FrameInterpolation_IgnoreActorMtx();
switch (enItem00->actor.params) {
case ITEM00_RUPEE_GREEN:
Matrix_Scale(25.0f, 25.0f, 25.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RUPEE_GREEN);
break;
case ITEM00_RUPEE_BLUE:
Matrix_Scale(25.0f, 25.0f, 25.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RUPEE_BLUE);
break;
case ITEM00_RUPEE_RED:
Matrix_Scale(25.0f, 25.0f, 25.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RUPEE_RED);
break;
case ITEM00_RUPEE_HUGE:
Matrix_Scale(17.5f, 17.5f, 17.5f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RUPEE_HUGE);
break;
case ITEM00_RUPEE_PURPLE:
Matrix_Scale(17.5f, 17.5f, 17.5f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RUPEE_PURPLE);
break;
case ITEM00_HEART_PIECE:
Matrix_Scale(16.0f, 16.0f, 16.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_HEART_PIECE);
break;
case ITEM00_HEART_CONTAINER:
Matrix_Scale(16.0f, 16.0f, 16.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_HEART_CONTAINER);
break;
case ITEM00_RECOVERY_HEART:
Matrix_Scale(16.0f, 16.0f, 16.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_RECOVERY_HEART);
break;
case ITEM00_BOMBS_A:
case ITEM00_BOMBS_B:
case ITEM00_BOMBS_0:
Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_BOMB);
break;
case ITEM00_ARROWS_10:
Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_ARROWS_SMALL);
break;
case ITEM00_ARROWS_30:
Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_ARROWS_MEDIUM);
break;
case ITEM00_ARROWS_40:
case ITEM00_ARROWS_50:
Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_ARROWS_LARGE);
break;
case ITEM00_MAGIC_JAR_SMALL:
Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_MAGIC_JAR_SMALL);
break;
case ITEM00_MAGIC_JAR_BIG:
Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_MAGIC_JAR_BIG);
break;
case ITEM00_DEKU_STICK:
Matrix_Scale(7.5f, 7.5f, 7.5f, MTXMODE_APPLY);
GetItem_Draw(play, GID_DEKU_STICK);
break;
case ITEM00_SMALL_KEY:
Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_KEY_SMALL);
break;
case ITEM00_DEKU_NUTS_1:
case ITEM00_DEKU_NUTS_10:
Matrix_Scale(9.0f, 9.0f, 9.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_DEKU_NUTS);
break;
case ITEM00_SHIELD_HERO:
GetItem_Draw(play, GID_SHIELD_HERO);
break;
case ITEM00_MAP:
GetItem_Draw(play, GID_DUNGEON_MAP);
break;
case ITEM00_COMPASS:
GetItem_Draw(play, GID_COMPASS);
break;
}
FrameInterpolation_RecordCloseChild();
}
}
void Tanron5_3DItemsDraw(Actor* actor, PlayState* play) {
EnTanron5* enTanron5 = (EnTanron5*)actor;
// Flicker when close to despawning
if ((enTanron5->timer <= 50) && !(enTanron5->timer & 1)) {
return;
}
// Tanron5 drops do not appear over Link's head when collected. That is vanilla behavior.
FrameInterpolation_RecordOpenChild(enTanron5, 0);
FrameInterpolation_IgnoreActorMtx();
// Move above the sand
Matrix_Translate(0.0f, 200.0f, 0.0f, MTXMODE_APPLY);
if (enTanron5->itemDropType == 0) { // TWINMOLD_PROP_ITEM_DROP_TYPE_10_ARROWS
Matrix_Scale(7.0f, 7.0f, 7.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_ARROWS_SMALL);
} else { // TWINMOLD_PROP_ITEM_DROP_TYPE_MAGIC_JAR_BIG
Matrix_Scale(8.0f, 8.0f, 8.0f, MTXMODE_APPLY);
GetItem_Draw(play, GID_MAGIC_JAR_BIG);
}
FrameInterpolation_RecordCloseChild();
}
void DrawSlime3DItem(Actor* actor, bool* should) {
*should = false;
EnSlime* slime = (EnSlime*)actor;
// Rotate 3D item with chu body
Matrix_RotateYS(slime->actor.shape.rot.y, MTXMODE_APPLY);
Matrix_Scale(0.25f, 0.25f, 0.25f, MTXMODE_APPLY);
switch (slime->actor.params) {
case EN_SLIME_TYPE_RED:
GetItem_Draw(gPlayState, GID_RECOVERY_HEART);
break;
case EN_SLIME_TYPE_GREEN:
GetItem_Draw(gPlayState, GID_MAGIC_JAR_SMALL);
break;
case EN_SLIME_TYPE_YELLOW:
GetItem_Draw(gPlayState, GID_ARROWS_SMALL);
break;
}
}
void Register3DItemDrops() {
if (gPlayState != NULL) {
// Regular item drops
Actor* actor = gPlayState->actorCtx.actorLists[ACTORCAT_MISC].first;
while (actor != NULL) {
if (actor->id == ACTOR_EN_ITEM00) {
if (CVAR && (actor->draw == EnItem00_Draw)) {
actor->draw = EnItem00_3DItemsDraw;
} else if (actor->draw == EnItem00_3DItemsDraw) {
actor->draw = EnItem00_Draw;
// Reset rotation for bill-boarded sprites
if (ItemShouldSpinWhen3D(actor)) {
actor->shape.rot.y = 0;
}
}
}
actor = actor->next;
}
// Twinmold boss fight arrow/magic drops
actor = gPlayState->actorCtx.actorLists[ACTORCAT_BOSS].first;
while (actor != NULL) {
if (actor->id == ACTOR_EN_TANRON5 &&
TWINMOLD_PROP_GET_TYPE(actor) >= TWINMOLD_PROP_TYPE_ITEM_DROP_1) { // Twinmold ruins drop
if (CVAR && (actor->draw == EnTanron5_ItemDrop_Draw)) {
actor->draw = Tanron5_3DItemsDraw;
} else if (actor->draw == Tanron5_3DItemsDraw) {
actor->draw = EnTanron5_ItemDrop_Draw;
// Reset rotation for bill-boarded sprites
actor->shape.rot.y = 0;
}
}
actor = actor->next;
}
}
COND_ID_HOOK(OnActorInit, ACTOR_EN_ITEM00, CVAR, [](Actor* actor) {
if (actor->draw == EnItem00_Draw) {
actor->draw = EnItem00_3DItemsDraw;
}
});
COND_ID_HOOK(OnActorUpdate, ACTOR_EN_ITEM00, CVAR, [](Actor* actor) {
// Add spin to normally bill-boarded items
if (actor->draw == EnItem00_3DItemsDraw && ItemShouldSpinWhen3D(actor)) {
actor->shape.rot.y += 0x3C0;
}
});
COND_ID_HOOK(OnActorInit, ACTOR_EN_TANRON5, CVAR, [](Actor* actor) {
if (actor->draw == EnTanron5_ItemDrop_Draw) {
actor->draw = Tanron5_3DItemsDraw;
}
});
COND_ID_HOOK(OnActorUpdate, ACTOR_EN_TANRON5, CVAR, [](Actor* actor) {
// Add spin
if (actor->draw == Tanron5_3DItemsDraw) {
actor->shape.rot.y += 0x3C0;
}
});
COND_VB_SHOULD(VB_DRAW_SLIME_BODY_ITEM, CVAR, {
Actor* actor = va_arg(args, Actor*);
DrawSlime3DItem(actor, should);
});
}
static RegisterShipInitFunc initFunc(Register3DItemDrops, { CVAR_NAME });
|