@starktma/minecraft-utils 1.3.25 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database/database.d.ts +6 -4
- package/dist/database/database.d.ts.map +1 -1
- package/dist/database/database.js +24 -11
- package/dist/database/database.js.map +1 -1
- package/dist/game-state-machine/database.d.ts +1 -1
- package/dist/game-state-machine/database.d.ts.map +1 -1
- package/dist/game-state-machine/database.js +1 -1
- package/dist/game-state-machine/database.js.map +1 -1
- package/dist/math/index.d.ts +63 -59
- package/dist/math/index.d.ts.map +1 -1
- package/dist/math/index.js +150 -76
- package/dist/math/index.js.map +1 -1
- package/dist/minecraft/effectsAPI.d.ts +44 -57
- package/dist/minecraft/effectsAPI.d.ts.map +1 -1
- package/dist/minecraft/effectsAPI.js +354 -351
- package/dist/minecraft/effectsAPI.js.map +1 -1
- package/dist/minecraft/index.d.ts +7 -7
- package/dist/minecraft/index.d.ts.map +1 -1
- package/dist/minecraft/index.js +45 -31
- package/dist/minecraft/index.js.map +1 -1
- package/dist/minecraft/projectileAPI.d.ts +21 -59
- package/dist/minecraft/projectileAPI.d.ts.map +1 -1
- package/dist/minecraft/projectileAPI.js +139 -137
- package/dist/minecraft/projectileAPI.js.map +1 -1
- package/dist/minecraft/structuresAPI.d.ts +10 -0
- package/dist/minecraft/structuresAPI.d.ts.map +1 -0
- package/dist/minecraft/structuresAPI.js +61 -0
- package/dist/minecraft/structuresAPI.js.map +1 -0
- package/package.json +5 -1
- package/dist/minecraft/potionAPI.d.ts +0 -74
- package/dist/minecraft/potionAPI.d.ts.map +0 -1
- package/dist/minecraft/potionAPI.js +0 -228
- package/dist/minecraft/potionAPI.js.map +0 -1
|
@@ -1,23 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EntityItemComponent, MolangVariableMap, system, world, } from "@minecraft/server";
|
|
2
2
|
import { MinecraftItemTypes } from "@minecraft/vanilla-data";
|
|
3
|
-
import { SimpleDatabase } from "@starktma/minecraft-utils/database";
|
|
4
3
|
import { getNamespace } from "../constants";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
constructor() {
|
|
8
|
-
super("
|
|
9
|
-
}
|
|
10
|
-
static getInstance() {
|
|
11
|
-
if (!EffectEntityDatabase.instance) {
|
|
12
|
-
EffectEntityDatabase.instance = new EffectEntityDatabase();
|
|
13
|
-
}
|
|
14
|
-
return EffectEntityDatabase.instance;
|
|
4
|
+
import { SimpleDatabase } from "../database";
|
|
5
|
+
class EffectDatabase extends SimpleDatabase {
|
|
6
|
+
constructor(entity) {
|
|
7
|
+
super("effects", entity);
|
|
15
8
|
}
|
|
16
9
|
}
|
|
10
|
+
// ============================= EffectManager =================================
|
|
17
11
|
class EffectManager {
|
|
18
|
-
configs = new Map();
|
|
19
|
-
entityDB = null;
|
|
20
12
|
static instance;
|
|
13
|
+
effectConfigs = new Map();
|
|
14
|
+
potionConfigs = new Map();
|
|
15
|
+
areaEffectClouds = new Map();
|
|
16
|
+
cloudIdCounter = 0;
|
|
17
|
+
trackedEntities = new Map();
|
|
18
|
+
// Entity databases: entityId -> EffectDatabase
|
|
19
|
+
entityDatabases = new Map();
|
|
21
20
|
static getInstance() {
|
|
22
21
|
if (!EffectManager.instance) {
|
|
23
22
|
EffectManager.instance = new EffectManager();
|
|
@@ -25,119 +24,69 @@ class EffectManager {
|
|
|
25
24
|
return EffectManager.instance;
|
|
26
25
|
}
|
|
27
26
|
constructor() { }
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* Track an entity as having effects
|
|
36
|
-
* @param entity - The entity to track
|
|
37
|
-
*/
|
|
38
|
-
trackEntity(entity) {
|
|
39
|
-
const db = this.getEntityDatabase();
|
|
40
|
-
const tracker = {
|
|
41
|
-
id: entity.id,
|
|
42
|
-
};
|
|
43
|
-
if (!db.hasObject(entity.id)) {
|
|
44
|
-
db.addObject(tracker);
|
|
27
|
+
// ============================= Database Helpers ==========================
|
|
28
|
+
// Get or create database for an entity
|
|
29
|
+
getEntityDatabase(entity) {
|
|
30
|
+
let db = this.entityDatabases.get(entity.id);
|
|
31
|
+
if (!db) {
|
|
32
|
+
db = new EffectDatabase(entity);
|
|
33
|
+
this.entityDatabases.set(entity.id, db);
|
|
45
34
|
}
|
|
35
|
+
return db;
|
|
46
36
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
*/
|
|
51
|
-
untrackEntity(entity) {
|
|
52
|
-
const db = this.getEntityDatabase();
|
|
53
|
-
if (db.hasObject(entity.id)) {
|
|
54
|
-
db.removeObject(entity.id);
|
|
55
|
-
}
|
|
37
|
+
getEffect(entity, effectType) {
|
|
38
|
+
const db = this.getEntityDatabase(entity);
|
|
39
|
+
return db.getObject(effectType) || null;
|
|
56
40
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
* @returns True if entity has any effect tags
|
|
61
|
-
*/
|
|
62
|
-
entityHasEffectTags(entity) {
|
|
63
|
-
return entity.getTags().some((tag) => tag.includes("starkteffects"));
|
|
41
|
+
getAllEffects(entity) {
|
|
42
|
+
const db = this.getEntityDatabase(entity);
|
|
43
|
+
return db.getAllObjects();
|
|
64
44
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
*/
|
|
70
|
-
getEffectsFromTags(entity) {
|
|
71
|
-
return entity
|
|
72
|
-
.getTags()
|
|
73
|
-
.filter((tag) => tag.includes("starkteffects"))
|
|
74
|
-
.map((tag) => {
|
|
75
|
-
const parts = tag.split("_");
|
|
76
|
-
if (parts.length < 4)
|
|
77
|
-
return null;
|
|
78
|
-
// Find the namespace and effects parts
|
|
79
|
-
const effectsIndex = parts.findIndex((part) => part === "starkteffects");
|
|
80
|
-
if (effectsIndex === -1 || effectsIndex === 0 || effectsIndex >= parts.length - 2)
|
|
81
|
-
return null;
|
|
82
|
-
const namespace = parts.slice(0, effectsIndex).join("_");
|
|
83
|
-
const effectType = parts.slice(effectsIndex + 1, -2).join("_");
|
|
84
|
-
const amplifier = parseInt(parts[parts.length - 2]);
|
|
85
|
-
const duration = parseInt(parts[parts.length - 1]);
|
|
86
|
-
const config = this.configs.get(effectType);
|
|
87
|
-
return {
|
|
88
|
-
id: `${entity.id}_${effectType}`,
|
|
89
|
-
entityId: entity.id,
|
|
90
|
-
effectType,
|
|
91
|
-
amplifier,
|
|
92
|
-
duration,
|
|
93
|
-
color: config?.color || { red: 1, green: 1, blue: 1, alpha: 1 },
|
|
94
|
-
};
|
|
95
|
-
})
|
|
96
|
-
.filter((effect) => effect !== null);
|
|
45
|
+
// Check if entity has any active effects and should be tracked
|
|
46
|
+
hasActiveEffects(entity) {
|
|
47
|
+
const db = this.getEntityDatabase(entity);
|
|
48
|
+
return db.getAllObjects().length > 0;
|
|
97
49
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
.
|
|
50
|
+
debug() {
|
|
51
|
+
for (const player of world.getAllPlayers()) {
|
|
52
|
+
const effects = this.getAllEffects(player);
|
|
53
|
+
if (effects.length === 0) {
|
|
54
|
+
player.onScreenDisplay.setActionBar("§8[§7Effects§8] §7No active effects");
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const effectMap = new Map();
|
|
58
|
+
for (const effect of effects) {
|
|
59
|
+
const existing = effectMap.get(effect.effectType);
|
|
60
|
+
if (!existing || effect.duration > existing.duration) {
|
|
61
|
+
effectMap.set(effect.effectType, effect);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const sorted = Array.from(effectMap.values()).sort((a, b) => b.duration - a.duration);
|
|
65
|
+
const display = sorted.map((effect) => {
|
|
66
|
+
const seconds = Math.ceil(effect.duration / 20);
|
|
67
|
+
const minutes = Math.floor(seconds / 60);
|
|
68
|
+
const remainingSeconds = seconds % 60;
|
|
69
|
+
const timeString = seconds > 60
|
|
70
|
+
? `§a${minutes}:${remainingSeconds.toString().padStart(2, "0")}`
|
|
71
|
+
: seconds > 10
|
|
72
|
+
? `§e${seconds}s`
|
|
73
|
+
: `§c${seconds}s`;
|
|
74
|
+
const name = effect.effectType
|
|
75
|
+
.split("_")
|
|
76
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
|
|
77
|
+
.join(" ");
|
|
78
|
+
const amp = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"][effect.amplifier] || effect.amplifier + 1;
|
|
79
|
+
return `§6${name}§r §7${amp}§r §8(${timeString}§8)§r`;
|
|
80
|
+
});
|
|
81
|
+
player.onScreenDisplay.setActionBar(`§8[§bActive Effects§8]§r\n${display.join("\n")}`);
|
|
82
|
+
}
|
|
130
83
|
}
|
|
131
|
-
|
|
132
|
-
* Register a effect effect with full configuration
|
|
133
|
-
* @param config - Complete effect configuration including effect type, color, sounds, particle type, and handler
|
|
134
|
-
* @returns Helper functions for adding, removing, and applying the effect
|
|
135
|
-
*/
|
|
84
|
+
// ============================= Effect API ================================
|
|
136
85
|
registerEffect(config) {
|
|
137
|
-
this.
|
|
86
|
+
this.effectConfigs.set(config.effectType, config);
|
|
138
87
|
return {
|
|
139
|
-
addEffect: (entity, amplifier,
|
|
140
|
-
this.addEffect(entity, config.effectType, amplifier,
|
|
88
|
+
addEffect: (entity, amplifier, durationTicks) => {
|
|
89
|
+
this.addEffect(entity, config.effectType, amplifier, durationTicks);
|
|
141
90
|
},
|
|
142
91
|
removeEffect: (entity) => {
|
|
143
92
|
this.removeEffect(entity, config.effectType);
|
|
@@ -146,293 +95,347 @@ class EffectManager {
|
|
|
146
95
|
};
|
|
147
96
|
}
|
|
148
97
|
addEffect(entity, effectType, amplifier, duration) {
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
// New effect - add tag
|
|
172
|
-
entity.addTag(`${currentNamespace}_starkteffects_${effectType}_${amplifier}_${duration * 20}`);
|
|
173
|
-
// Play start sound if configured
|
|
174
|
-
if (config?.sounds?.start) {
|
|
98
|
+
const config = this.effectConfigs.get(effectType);
|
|
99
|
+
const db = this.getEntityDatabase(entity);
|
|
100
|
+
const existing = db.getObject(effectType);
|
|
101
|
+
// Determine which effect to keep (stronger one)
|
|
102
|
+
if (!existing ||
|
|
103
|
+
amplifier > existing.amplifier ||
|
|
104
|
+
(amplifier === existing.amplifier && duration > existing.duration)) {
|
|
105
|
+
const effect = {
|
|
106
|
+
id: effectType,
|
|
107
|
+
namespace: getNamespace(),
|
|
108
|
+
effectType,
|
|
109
|
+
amplifier,
|
|
110
|
+
duration,
|
|
111
|
+
color: config?.color || { red: 1, green: 1, blue: 1, alpha: 1 },
|
|
112
|
+
};
|
|
113
|
+
db.updateObject(effect);
|
|
114
|
+
// Play start sound only for new effects
|
|
115
|
+
if (!existing && config?.sounds?.start) {
|
|
175
116
|
try {
|
|
176
117
|
entity.dimension.playSound(config.sounds.start, entity.location);
|
|
177
118
|
}
|
|
178
|
-
catch
|
|
119
|
+
catch { }
|
|
179
120
|
}
|
|
180
121
|
}
|
|
181
|
-
// Track
|
|
182
|
-
this.
|
|
122
|
+
// Track entity for effect updates
|
|
123
|
+
this.trackedEntities.set(entity.id, entity);
|
|
183
124
|
}
|
|
184
125
|
removeEffect(entity, effectType) {
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
entity.dimension.playSound(config.sounds.end, entity.location);
|
|
196
|
-
}
|
|
197
|
-
catch (error) { }
|
|
198
|
-
}
|
|
199
|
-
// Remove effect tags
|
|
200
|
-
effectTags.forEach((tag) => {
|
|
201
|
-
entity.removeTag(tag);
|
|
202
|
-
});
|
|
203
|
-
// Check if entity still has any effects, untrack if not
|
|
204
|
-
if (!this.entityHasEffectTags(entity)) {
|
|
205
|
-
this.untrackEntity(entity);
|
|
126
|
+
const db = this.getEntityDatabase(entity);
|
|
127
|
+
const hasEffect = db.hasObject(effectType);
|
|
128
|
+
const config = this.effectConfigs.get(effectType);
|
|
129
|
+
// Remove from database
|
|
130
|
+
db.removeObject(effectType);
|
|
131
|
+
// Play end sound only if effect existed
|
|
132
|
+
if (hasEffect && config?.sounds?.end) {
|
|
133
|
+
try {
|
|
134
|
+
entity.dimension.playSound(config.sounds.end, entity.location);
|
|
206
135
|
}
|
|
136
|
+
catch { }
|
|
137
|
+
}
|
|
138
|
+
// Untrack entity if no effects remain
|
|
139
|
+
if (!this.hasActiveEffects(entity)) {
|
|
140
|
+
this.entityDatabases.delete(entity.id);
|
|
141
|
+
this.trackedEntities.delete(entity.id);
|
|
207
142
|
}
|
|
208
143
|
}
|
|
209
144
|
removeAllEffects(entity) {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
effectTypes.add(effectType);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
effectTypes.forEach((effectType) => {
|
|
223
|
-
const config = this.configs.get(effectType);
|
|
145
|
+
const db = this.getEntityDatabase(entity);
|
|
146
|
+
const effects = db.getAllObjects();
|
|
147
|
+
entity
|
|
148
|
+
.getTags()
|
|
149
|
+
.filter((tag) => tag.startsWith("starkeffects|pause_effect"))
|
|
150
|
+
.forEach((tag) => entity.removeTag(tag));
|
|
151
|
+
// Play end sounds for each effect
|
|
152
|
+
for (const effect of effects) {
|
|
153
|
+
const config = this.effectConfigs.get(effect.effectType);
|
|
224
154
|
if (config?.sounds?.end) {
|
|
225
155
|
try {
|
|
226
156
|
entity.dimension.playSound(config.sounds.end, entity.location);
|
|
227
157
|
}
|
|
228
|
-
catch
|
|
158
|
+
catch { }
|
|
229
159
|
}
|
|
230
|
-
}
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
// Untrack this entity since it has no more effects
|
|
236
|
-
this.untrackEntity(entity);
|
|
237
|
-
}
|
|
238
|
-
extendEffect(entity, effectType, additionalDuration) {
|
|
239
|
-
// Debug: Show all tags on the entity
|
|
240
|
-
const allTags = entity.getTags();
|
|
241
|
-
// Find effect tags of any namespace
|
|
242
|
-
const effectTags = entity.getTags().filter((tag) => {
|
|
243
|
-
// Tag format: {namespace}_starkteffects_{effectType}_{amplifier}_{duration}
|
|
244
|
-
// We need to find the "_starkteffects_" marker and work from there
|
|
245
|
-
const effectsIndex = tag.indexOf("_starkteffects_");
|
|
246
|
-
if (effectsIndex === -1)
|
|
247
|
-
return false;
|
|
248
|
-
// Extract parts after "_starkteffects_"
|
|
249
|
-
const afterEffects = tag.substring(effectsIndex + 15); // 15 = "_starkteffects_".length
|
|
250
|
-
const parts = afterEffects.split("_");
|
|
251
|
-
// Need at least 3 parts: effectType, amplifier, duration
|
|
252
|
-
// But effectType might contain underscores, so amplifier and duration are the last 2 parts
|
|
253
|
-
if (parts.length < 3)
|
|
254
|
-
return false;
|
|
255
|
-
// Extract effect type (everything except the last 2 parts which are amplifier and duration)
|
|
256
|
-
const tagEffectType = parts.slice(0, -2).join("_");
|
|
257
|
-
return tagEffectType === effectType;
|
|
258
|
-
});
|
|
259
|
-
if (effectTags.length === 0)
|
|
260
|
-
return false;
|
|
261
|
-
// For each matching effect tag, extend its duration
|
|
262
|
-
effectTags.forEach((tag) => {
|
|
263
|
-
// Tag format: {namespace}_starkteffects_{effectType}_{amplifier}_{duration}
|
|
264
|
-
const effectsIndex = tag.indexOf("_starkteffects_");
|
|
265
|
-
const namespace = tag.substring(0, effectsIndex);
|
|
266
|
-
const afterEffects = tag.substring(effectsIndex + 15); // 15 = "_starkteffects_".length
|
|
267
|
-
const parts = afterEffects.split("_");
|
|
268
|
-
// Last two parts are always amplifier and duration
|
|
269
|
-
const amplifier = parseInt(parts[parts.length - 2]);
|
|
270
|
-
const currentDuration = parseInt(parts[parts.length - 1]);
|
|
271
|
-
const newDuration = currentDuration + additionalDuration;
|
|
272
|
-
// Remove old tag and add new one with extended duration
|
|
273
|
-
entity.removeTag(tag);
|
|
274
|
-
const newTag = `${namespace}_starkteffects_${effectType}_${amplifier}_${newDuration}`;
|
|
275
|
-
entity.addTag(newTag);
|
|
276
|
-
});
|
|
277
|
-
return true;
|
|
160
|
+
}
|
|
161
|
+
// Clear database and untrack
|
|
162
|
+
db.eraseAllObjects();
|
|
163
|
+
this.entityDatabases.delete(entity.id);
|
|
164
|
+
this.trackedEntities.delete(entity.id);
|
|
278
165
|
}
|
|
279
166
|
hasEffect(entity, effectType) {
|
|
280
|
-
const
|
|
281
|
-
return
|
|
167
|
+
const db = this.getEntityDatabase(entity);
|
|
168
|
+
return db.hasObject(effectType);
|
|
282
169
|
}
|
|
283
170
|
getEffects(entity) {
|
|
284
|
-
|
|
171
|
+
const db = this.getEntityDatabase(entity);
|
|
172
|
+
return db.getAllObjects();
|
|
285
173
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const effectTags = entity.getTags().filter((tag) => tag.includes(`starkteffects`));
|
|
298
|
-
if (effectTags.length === 0) {
|
|
299
|
-
entity.onScreenDisplay.setActionBar("§8[§7Effects§8] §7No active effects");
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
// Parse effect data from tags
|
|
303
|
-
const effects = effectTags
|
|
304
|
-
.map((tag) => {
|
|
305
|
-
// Format: namespace_starkteffects_${effectType}_${amplifier}_${duration}
|
|
306
|
-
const parts = tag.split("_");
|
|
307
|
-
if (parts.length < 4)
|
|
308
|
-
return null;
|
|
309
|
-
const effectType = parts.slice(2, -2).join("_"); // Handle effect types with underscores
|
|
310
|
-
const amplifier = parseInt(parts[parts.length - 2]);
|
|
311
|
-
const duration = parseInt(parts[parts.length - 1]);
|
|
312
|
-
return { effectType, amplifier, duration };
|
|
313
|
-
})
|
|
314
|
-
.filter((effect) => effect !== null);
|
|
315
|
-
if (effects.length === 0) {
|
|
316
|
-
entity.onScreenDisplay.setActionBar("§8[§7Effects§8] §7No active effects");
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
// Sort effects by remaining duration (longest first)
|
|
320
|
-
const sortedEffects = effects.sort((a, b) => b.duration - a.duration);
|
|
321
|
-
const effectStrings = sortedEffects.map((effect) => {
|
|
322
|
-
const seconds = Math.ceil(effect.duration / 20);
|
|
323
|
-
const minutes = Math.floor(seconds / 60);
|
|
324
|
-
const remainingSeconds = seconds % 60;
|
|
325
|
-
// Format time with different colors based on urgency
|
|
326
|
-
let timeString;
|
|
327
|
-
let timeColor;
|
|
328
|
-
if (seconds > 60) {
|
|
329
|
-
timeString = `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
|
|
330
|
-
timeColor = "§a"; // Green for long duration
|
|
331
|
-
}
|
|
332
|
-
else if (seconds > 10) {
|
|
333
|
-
timeString = `${seconds}s`;
|
|
334
|
-
timeColor = "§e"; // Yellow for medium duration
|
|
335
|
-
}
|
|
336
|
-
else {
|
|
337
|
-
timeString = `${seconds}s`;
|
|
338
|
-
timeColor = "§c"; // Red for short duration
|
|
339
|
-
}
|
|
340
|
-
// Format effect name with proper capitalization
|
|
341
|
-
const effectName = effect.effectType.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1))[1];
|
|
342
|
-
// Format amplifier with Roman numerals for style
|
|
343
|
-
const romanNumerals = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"];
|
|
344
|
-
const amplifierDisplay = effect.amplifier < romanNumerals.length ? romanNumerals[effect.amplifier] : (effect.amplifier + 1).toString();
|
|
345
|
-
return `§6${effectName}§r §7${amplifierDisplay}§r §8(${timeColor}${timeString}§8)§r`;
|
|
346
|
-
});
|
|
347
|
-
// Create a formatted display with header and effects
|
|
348
|
-
const header = "§8[§bActive Effects§8]§r";
|
|
349
|
-
const effectsDisplay = effectStrings.join("\n");
|
|
350
|
-
const displayText = `${header}\n${effectsDisplay}`;
|
|
351
|
-
entity.onScreenDisplay.setActionBar(displayText);
|
|
174
|
+
// ============================= Potion API ================================
|
|
175
|
+
registerPotion(config) {
|
|
176
|
+
const fullConfig = {
|
|
177
|
+
splashRange: 4,
|
|
178
|
+
lingeringMaxRadius: 3,
|
|
179
|
+
lingeringLifetime: 600,
|
|
180
|
+
lingeringDurationMultiplier: 0.25,
|
|
181
|
+
...config,
|
|
182
|
+
};
|
|
183
|
+
this.potionConfigs.set(`${config.namespace}:${config.effectKey}`, fullConfig);
|
|
184
|
+
return { handler: config.handler };
|
|
352
185
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
186
|
+
// ============================= System ====================================
|
|
187
|
+
start(startup, debug) {
|
|
188
|
+
if (startup) {
|
|
189
|
+
for (const [, config] of this.potionConfigs.entries()) {
|
|
190
|
+
const componentId = `${config.namespace}:${config.effectKey}_potion_effect`;
|
|
191
|
+
startup.itemComponentRegistry.registerCustomComponent(componentId, {
|
|
192
|
+
onConsume: (event, params) => {
|
|
193
|
+
const p = params.params;
|
|
194
|
+
const color = {
|
|
195
|
+
red: p.potion_color[0],
|
|
196
|
+
green: p.potion_color[1],
|
|
197
|
+
blue: p.potion_color[2],
|
|
198
|
+
alpha: p.potion_color[3],
|
|
199
|
+
};
|
|
200
|
+
config.handler(event.source, p.amplifier, p.duration * 20, color);
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
world.afterEvents.projectileHitBlock.subscribe((event) => this.handleProjectileHit(event.projectile));
|
|
205
|
+
world.afterEvents.projectileHitEntity.subscribe((event) => this.handleProjectileHit(event.projectile));
|
|
206
|
+
}
|
|
356
207
|
system.runInterval(() => {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (!entity)
|
|
208
|
+
//if (debug) {
|
|
209
|
+
// this.debug();
|
|
210
|
+
//}
|
|
211
|
+
// Process effects on tracked entities only
|
|
212
|
+
for (const [entityId, entity] of this.trackedEntities.entries()) {
|
|
213
|
+
if (!entity.isValid) {
|
|
214
|
+
this.entityDatabases.delete(entityId);
|
|
215
|
+
this.trackedEntities.delete(entityId);
|
|
363
216
|
continue;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
217
|
+
}
|
|
218
|
+
const allTags = entity.getTags();
|
|
219
|
+
const pauseEffects = allTags.filter((tag) => tag.startsWith(`starkeffects|pause_effect`));
|
|
220
|
+
const db = this.getEntityDatabase(entity);
|
|
221
|
+
const effects = db.getAllObjects();
|
|
222
|
+
if (effects.length === 0) {
|
|
223
|
+
this.entityDatabases.delete(entityId);
|
|
224
|
+
this.trackedEntities.delete(entityId);
|
|
367
225
|
continue;
|
|
368
226
|
}
|
|
369
|
-
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
.some((tag) => tag.startsWith(`${currentNamespace}_starkteffects_${effect.effectType}_`));
|
|
376
|
-
});
|
|
227
|
+
// Store original durations to detect handler modifications
|
|
228
|
+
const originalDurations = new Map();
|
|
229
|
+
for (const effect of effects) {
|
|
230
|
+
originalDurations.set(effect.effectType, effect.duration);
|
|
231
|
+
}
|
|
232
|
+
// Apply handlers and update effects
|
|
377
233
|
for (const effect of effects) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
// Apply effect handler
|
|
234
|
+
const config = this.effectConfigs.get(effect.effectType);
|
|
235
|
+
// Run effect handler
|
|
381
236
|
if (config?.handler) {
|
|
382
237
|
config.handler(entity, effect);
|
|
383
238
|
}
|
|
384
|
-
//
|
|
239
|
+
// Get current effect from database (handler may have modified it)
|
|
240
|
+
const currentEffect = db.getObject(effect.effectType);
|
|
241
|
+
if (!currentEffect)
|
|
242
|
+
continue;
|
|
243
|
+
// Check if handler modified the duration
|
|
244
|
+
const originalDuration = originalDurations.get(effect.effectType);
|
|
245
|
+
if (currentEffect.duration !== originalDuration) {
|
|
246
|
+
// Handler modified it, skip countdown
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
// Play ambient sounds
|
|
385
250
|
if (config?.sounds?.ambient && system.currentTick % 40 === 0) {
|
|
386
251
|
try {
|
|
387
252
|
entity.dimension.playSound(config.sounds.ambient, entity.location, { volume: 0.5 });
|
|
388
253
|
}
|
|
389
|
-
catch
|
|
254
|
+
catch { }
|
|
390
255
|
}
|
|
391
256
|
// Spawn particles
|
|
392
|
-
if (system.currentTick %
|
|
393
|
-
const
|
|
394
|
-
particleLocation.y++;
|
|
257
|
+
if (system.currentTick % 20 === 0) {
|
|
258
|
+
const loc = { ...entity.location, y: entity.location.y + 1 };
|
|
395
259
|
const variables = new MolangVariableMap();
|
|
396
|
-
variables.setColorRGBA("color",
|
|
397
|
-
const particleType = config?.particleType || "minecraft:mobspell_emitter";
|
|
260
|
+
variables.setColorRGBA("color", currentEffect.color);
|
|
398
261
|
try {
|
|
399
|
-
entity.dimension.spawnParticle(particleType,
|
|
262
|
+
entity.dimension.spawnParticle(config?.particleType || "minecraft:mobspell_emitter", loc, variables);
|
|
400
263
|
}
|
|
401
|
-
catch
|
|
264
|
+
catch { }
|
|
402
265
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
266
|
+
if (pauseEffects.length > 0) {
|
|
267
|
+
const excludeEffects = pauseEffects.map((tag) => tag.split("|")[2]);
|
|
268
|
+
if (!excludeEffects.includes(effect.effectType))
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
// Countdown duration
|
|
272
|
+
const newDuration = currentEffect.duration - 1;
|
|
273
|
+
if (newDuration > 0) {
|
|
274
|
+
db.updateObject({ ...currentEffect, duration: newDuration });
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
db.removeObject(effect.effectType);
|
|
407
278
|
if (config?.sounds?.end) {
|
|
408
279
|
try {
|
|
409
280
|
entity.dimension.playSound(config.sounds.end, entity.location);
|
|
410
281
|
}
|
|
411
|
-
catch
|
|
282
|
+
catch { }
|
|
412
283
|
}
|
|
413
|
-
// Remove expired effect tag
|
|
414
|
-
const currentNamespace = getNamespace();
|
|
415
|
-
entity.removeTag(`${currentNamespace}_starkteffects_${effect.effectType}_${effect.amplifier}_${effect.duration}`);
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
// Update tag with new duration
|
|
419
|
-
const currentNamespace = getNamespace();
|
|
420
|
-
entity.removeTag(`${currentNamespace}_starkteffects_${effect.effectType}_${effect.amplifier}_${effect.duration}`);
|
|
421
|
-
entity.addTag(`${currentNamespace}_starkteffects_${effect.effectType}_${effect.amplifier}_${newDuration}`);
|
|
422
284
|
}
|
|
423
285
|
}
|
|
424
|
-
//
|
|
425
|
-
if (!this.
|
|
426
|
-
|
|
286
|
+
// Untrack if no effects remain
|
|
287
|
+
if (!this.hasActiveEffects(entity)) {
|
|
288
|
+
this.entityDatabases.delete(entityId);
|
|
289
|
+
this.trackedEntities.delete(entityId);
|
|
427
290
|
}
|
|
428
291
|
}
|
|
292
|
+
for (const cloud of this.areaEffectClouds.values()) {
|
|
293
|
+
this.updateCloud(cloud);
|
|
294
|
+
}
|
|
295
|
+
}, 1);
|
|
296
|
+
// Track entities that spawn with active effects
|
|
297
|
+
world.afterEvents.entitySpawn.subscribe((event) => {
|
|
298
|
+
if (!event.entity || !event.entity.isValid)
|
|
299
|
+
return;
|
|
300
|
+
if (event.entity.hasComponent(EntityItemComponent.componentId))
|
|
301
|
+
return;
|
|
302
|
+
if (this.hasActiveEffects(event.entity)) {
|
|
303
|
+
this.trackedEntities.set(event.entity.id, event.entity);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
world.afterEvents.playerJoin.subscribe((event) => {
|
|
307
|
+
const player = world.getEntity(event.playerId);
|
|
308
|
+
if (this.hasActiveEffects(player)) {
|
|
309
|
+
this.trackedEntities.set(player.id, player);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
// Clean up when entities die
|
|
313
|
+
world.afterEvents.entityDie.subscribe((event) => {
|
|
314
|
+
this.entityDatabases.delete(event.deadEntity.id);
|
|
315
|
+
this.trackedEntities.delete(event.deadEntity.id);
|
|
429
316
|
});
|
|
430
317
|
world.afterEvents.itemCompleteUse.subscribe((event) => {
|
|
431
318
|
if (event.itemStack.typeId === MinecraftItemTypes.MilkBucket) {
|
|
432
319
|
this.removeAllEffects(event.source);
|
|
433
320
|
}
|
|
434
321
|
});
|
|
322
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
323
|
+
world.getAllPlayers().forEach((player) => {
|
|
324
|
+
if (this.hasActiveEffects(player)) {
|
|
325
|
+
this.trackedEntities.set(player.id, player);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
// ============================= Potion Projectiles ========================
|
|
331
|
+
handleProjectileHit(projectile) {
|
|
332
|
+
const configs = Array.from(this.potionConfigs.values());
|
|
333
|
+
for (const config of configs) {
|
|
334
|
+
if (projectile.typeId !== `${config.namespace}:potion_projectile`)
|
|
335
|
+
continue;
|
|
336
|
+
const effectId = projectile.getProperty(`${config.namespace}:effect_id`);
|
|
337
|
+
if (effectId !== config.effectId)
|
|
338
|
+
continue;
|
|
339
|
+
const potionType = projectile.getProperty(`${config.namespace}:type`);
|
|
340
|
+
if (potionType === 1) {
|
|
341
|
+
this.handleSplash(projectile, config);
|
|
342
|
+
}
|
|
343
|
+
else if (potionType === 2) {
|
|
344
|
+
this.handleLingering(projectile, config);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
projectile.remove();
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
handleSplash(projectile, config) {
|
|
353
|
+
const location = { ...projectile.location, y: projectile.location.y + 0.1 };
|
|
354
|
+
const color = this.getColorFromEntity(projectile, config.namespace);
|
|
355
|
+
const amplifier = projectile.getProperty(`${config.namespace}:amplifier`);
|
|
356
|
+
const duration = projectile.getProperty(`${config.namespace}:duration`) * 20;
|
|
357
|
+
const variables = new MolangVariableMap();
|
|
358
|
+
variables.setFloat("splash_range", config.splashRange);
|
|
359
|
+
variables.setFloat("splash_power", 1);
|
|
360
|
+
variables.setColorRGBA("color", color);
|
|
361
|
+
projectile.dimension.spawnParticle("minecraft:splash_spell_emitter", location, variables);
|
|
362
|
+
const nearbyEntities = projectile.dimension.getEntities({
|
|
363
|
+
excludeTypes: ["minecraft:item", "minecraft:arrow"],
|
|
364
|
+
location: projectile.location,
|
|
365
|
+
maxDistance: config.splashRange,
|
|
366
|
+
});
|
|
367
|
+
for (const entity of nearbyEntities) {
|
|
368
|
+
if (entity.id !== projectile.id) {
|
|
369
|
+
config.handler(entity, amplifier, duration, color);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
projectile.remove();
|
|
373
|
+
}
|
|
374
|
+
handleLingering(projectile, config) {
|
|
375
|
+
const location = { ...projectile.location, y: projectile.location.y + 0.1 };
|
|
376
|
+
const color = this.getColorFromEntity(projectile, config.namespace);
|
|
377
|
+
const amplifier = projectile.getProperty(`${config.namespace}:amplifier`);
|
|
378
|
+
const duration = projectile.getProperty(`${config.namespace}:duration`) * 20;
|
|
379
|
+
const cloud = {
|
|
380
|
+
id: `aoe_cloud_${this.cloudIdCounter++}`,
|
|
381
|
+
dimension: projectile.dimension.id,
|
|
382
|
+
location,
|
|
383
|
+
color,
|
|
384
|
+
amplifier,
|
|
385
|
+
duration,
|
|
386
|
+
currentRadius: config.lingeringMaxRadius,
|
|
387
|
+
maxRadius: config.lingeringMaxRadius,
|
|
388
|
+
createdTick: system.currentTick,
|
|
389
|
+
maxLifetime: config.lingeringLifetime,
|
|
390
|
+
affectedEntities: new Set(),
|
|
391
|
+
handler: config.handler,
|
|
392
|
+
};
|
|
393
|
+
this.areaEffectClouds.set(cloud.id, cloud);
|
|
394
|
+
projectile.remove();
|
|
395
|
+
}
|
|
396
|
+
updateCloud(cloud) {
|
|
397
|
+
const dimension = world.getDimension(cloud.dimension);
|
|
398
|
+
const age = system.currentTick - cloud.createdTick;
|
|
399
|
+
const ageProgress = Math.min(age / cloud.maxLifetime, 1);
|
|
400
|
+
cloud.currentRadius = Math.max(0, cloud.maxRadius * (1 - ageProgress));
|
|
401
|
+
if (age >= cloud.maxLifetime || cloud.currentRadius <= 0) {
|
|
402
|
+
this.areaEffectClouds.delete(cloud.id);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const variables = new MolangVariableMap();
|
|
406
|
+
variables.setFloat("cloud_lifetime", cloud.maxLifetime / 20);
|
|
407
|
+
variables.setFloat("cloud_radius", cloud.currentRadius);
|
|
408
|
+
variables.setFloat("particle_multiplier", cloud.currentRadius / cloud.maxRadius);
|
|
409
|
+
variables.setColorRGBA("color", cloud.color);
|
|
410
|
+
dimension.spawnParticle("minecraft:mobspell_lingering", cloud.location, variables);
|
|
411
|
+
if (age < 20)
|
|
412
|
+
return;
|
|
413
|
+
const nearbyEntities = dimension.getEntities({
|
|
414
|
+
location: cloud.location,
|
|
415
|
+
maxDistance: cloud.currentRadius + 1,
|
|
416
|
+
excludeTypes: ["minecraft:item", "minecraft:arrow", "minecraft:xp_orb"],
|
|
417
|
+
});
|
|
418
|
+
for (const entity of nearbyEntities) {
|
|
419
|
+
if (cloud.affectedEntities.has(entity.id))
|
|
420
|
+
continue;
|
|
421
|
+
cloud.handler(entity, cloud.amplifier, Math.floor(cloud.duration), cloud.color);
|
|
422
|
+
cloud.currentRadius = Math.max(0, cloud.currentRadius - 0.5);
|
|
423
|
+
cloud.maxLifetime = Math.max(0, cloud.maxLifetime - 100);
|
|
424
|
+
cloud.affectedEntities.add(entity.id);
|
|
425
|
+
system.runTimeout(() => {
|
|
426
|
+
cloud.affectedEntities.delete(entity.id);
|
|
427
|
+
}, 60);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
getColorFromEntity(entity, namespace) {
|
|
431
|
+
return {
|
|
432
|
+
red: entity.getProperty(`${namespace}:color_r`),
|
|
433
|
+
green: entity.getProperty(`${namespace}:color_g`),
|
|
434
|
+
blue: entity.getProperty(`${namespace}:color_b`),
|
|
435
|
+
alpha: entity.getProperty(`${namespace}:color_a`),
|
|
436
|
+
};
|
|
435
437
|
}
|
|
436
438
|
}
|
|
437
|
-
|
|
439
|
+
const effectManager = EffectManager.getInstance();
|
|
440
|
+
export { EffectManager, effectManager };
|
|
438
441
|
//# sourceMappingURL=effectsAPI.js.map
|