warcraft-3-w3ts-utils 0.1.1
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/LICENSE +21 -0
- package/package.json +34 -0
- package/src/utils/abilities.ts +160 -0
- package/src/utils/camera.ts +63 -0
- package/src/utils/chat-command.ts +22 -0
- package/src/utils/color.ts +139 -0
- package/src/utils/item.ts +155 -0
- package/src/utils/math.ts +14 -0
- package/src/utils/minimapIcons.ts +34 -0
- package/src/utils/misc.ts +179 -0
- package/src/utils/physics.ts +295 -0
- package/src/utils/players.ts +214 -0
- package/src/utils/point.ts +81 -0
- package/src/utils/quests.ts +38 -0
- package/src/utils/textTag.ts +80 -0
- package/src/utils/timer.ts +14 -0
- package/src/utils/units.ts +109 -0
- package/tsconfig.json +34 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Eddy Estrada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "warcraft-3-w3ts-utils",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A library of utility functions for modding Warcraft 3 utilizing the W3TS template.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npm run test"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/bertman12/WC3-W3TS-utils.git"
|
|
12
|
+
},
|
|
13
|
+
"author": "bertman12",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/bertman12/WC3-W3TS-utils/issues"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/bertman12/WC3-W3TS-utils#readme",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"war3-types-strict": "^0.1.3"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"lua-types": "^2.13.1",
|
|
24
|
+
"typescript-to-lua": "1.31.0",
|
|
25
|
+
"war3-objectdata-th": "^0.2.11",
|
|
26
|
+
"war3-transformer": "^3.0.10",
|
|
27
|
+
"war3tstlhelper": "1.0.1"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"typescript": "^5.0.4",
|
|
31
|
+
"w3ts": "3.0.2",
|
|
32
|
+
"lua-types": "^2.13.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { Timer, Trigger, Unit } from "w3ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Good for once a unit gets near you, but not good while they are near you since the event is only triggered on entry, not during
|
|
5
|
+
* @param unit
|
|
6
|
+
* @param range
|
|
7
|
+
* @param cb
|
|
8
|
+
* @param config
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export function unitGetsNearThisUnit(
|
|
12
|
+
unit: Unit,
|
|
13
|
+
range: number,
|
|
14
|
+
cb: (u: Unit) => void,
|
|
15
|
+
config?: {
|
|
16
|
+
uniqueUnitsOnly: boolean;
|
|
17
|
+
filter?: boolexpr | (() => boolean);
|
|
18
|
+
onDestroy?: (unitsEffected: Unit[]) => void;
|
|
19
|
+
}
|
|
20
|
+
) {
|
|
21
|
+
const trig = Trigger.create();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A unique set of the units effected
|
|
25
|
+
*/
|
|
26
|
+
let effectedUnitPool: Unit[] = [];
|
|
27
|
+
|
|
28
|
+
trig.registerUnitInRage(unit.handle, range, config?.filter ?? (() => true));
|
|
29
|
+
|
|
30
|
+
trig.addAction(() => {
|
|
31
|
+
const u = Unit.fromEvent();
|
|
32
|
+
|
|
33
|
+
if (!u) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!effectedUnitPool.includes(u)) {
|
|
38
|
+
effectedUnitPool.push(u);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (config?.uniqueUnitsOnly && !effectedUnitPool.includes(u)) {
|
|
42
|
+
cb(u);
|
|
43
|
+
} else {
|
|
44
|
+
cb(u);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const destroy = () => {
|
|
49
|
+
if (config?.onDestroy) {
|
|
50
|
+
config?.onDestroy(effectedUnitPool);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// print("destroying the trigger!");
|
|
54
|
+
trig.destroy();
|
|
55
|
+
// print("The trigger ref: ", trig);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const clearUniqueUnits = () => {
|
|
59
|
+
effectedUnitPool = [];
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
cleanupUnitGetsNearThisUnit: (delay?: number) => {
|
|
64
|
+
if (delay) {
|
|
65
|
+
const timer = Timer.create();
|
|
66
|
+
timer.start(delay, false, () => {
|
|
67
|
+
destroy();
|
|
68
|
+
timer.destroy();
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
destroy();
|
|
74
|
+
},
|
|
75
|
+
clearUniqueUnits,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
*
|
|
81
|
+
* @param cb
|
|
82
|
+
* @param abilityId
|
|
83
|
+
* @param dummyLifeTime Maybe be necessary to have a long lifetime so spells like chain lightning will have time to bounce to all targets
|
|
84
|
+
* @param owner
|
|
85
|
+
*/
|
|
86
|
+
// export function useTempDummyUnit(cb: (dummy: Unit) => void, abilityId: number, dummyLifeTime: number, owner: MapPlayer, x: number, y: number, facing: number, config?: { abilityLevel?: number; modelType?: "cenariusGhost" }) {
|
|
87
|
+
// let dummy: Unit | undefined = undefined;
|
|
88
|
+
|
|
89
|
+
// if (config?.modelType === "cenariusGhost") {
|
|
90
|
+
// dummy = Unit.create(owner, UNITS.dummyCaster_cenariusGhost, x, y, facing);
|
|
91
|
+
// dummy?.setScale(1, 1, 1);
|
|
92
|
+
// } else {
|
|
93
|
+
// dummy = Unit.create(owner, UNITS.dummyCaster, x, y, facing);
|
|
94
|
+
// }
|
|
95
|
+
|
|
96
|
+
// const t = Timer.create();
|
|
97
|
+
|
|
98
|
+
// if (dummy) {
|
|
99
|
+
// dummy.addAbility(abilityId);
|
|
100
|
+
// dummy.setAbilityManaCost(abilityId, config?.abilityLevel ? config.abilityLevel - 1 : 0, 0);
|
|
101
|
+
// cb(dummy);
|
|
102
|
+
|
|
103
|
+
// t.start(dummyLifeTime, false, () => {
|
|
104
|
+
// dummy?.destroy();
|
|
105
|
+
// t.destroy();
|
|
106
|
+
// });
|
|
107
|
+
// }
|
|
108
|
+
// }
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Creates a trigger to monitor when a unit is attacked
|
|
112
|
+
*
|
|
113
|
+
* We could also have all functions execute in this single trigger's context instead of creating new triggers each time the function is used.
|
|
114
|
+
* @param cb
|
|
115
|
+
* @param config
|
|
116
|
+
*/
|
|
117
|
+
export function onUnitAttacked(
|
|
118
|
+
cb: (attacker: Unit, victim: Unit) => void,
|
|
119
|
+
config: { attackerCooldown?: boolean; procChance?: number }
|
|
120
|
+
) {
|
|
121
|
+
const attackerTriggerCooldown = new Set<Unit>();
|
|
122
|
+
const t = Trigger.create();
|
|
123
|
+
|
|
124
|
+
t.registerAnyUnitEvent(EVENT_PLAYER_UNIT_ATTACKED);
|
|
125
|
+
|
|
126
|
+
t.addAction(() => {
|
|
127
|
+
const attacker = Unit.fromHandle(GetAttacker());
|
|
128
|
+
const victim = Unit.fromHandle(GetAttackedUnitBJ());
|
|
129
|
+
|
|
130
|
+
if (!attacker || !victim) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
//Attack was not below the proc chance, and thus we will not use the cb function
|
|
135
|
+
if (
|
|
136
|
+
config.procChance &&
|
|
137
|
+
Math.ceil(Math.random() * 100) >= config.procChance
|
|
138
|
+
) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//Attacker has already used the trigger
|
|
143
|
+
if (config.attackerCooldown && attackerTriggerCooldown.has(attacker)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
attackerTriggerCooldown.add(attacker);
|
|
148
|
+
|
|
149
|
+
//Finally, after all conditions have been met, use the cb function
|
|
150
|
+
cb(attacker, victim);
|
|
151
|
+
|
|
152
|
+
const t = Timer.create();
|
|
153
|
+
|
|
154
|
+
//removes the attacker from the cooldown group after 1/3 of that units attack cooldown has passed.
|
|
155
|
+
t.start(attacker.getAttackCooldown(0) / 3, false, () => {
|
|
156
|
+
attackerTriggerCooldown.delete(attacker);
|
|
157
|
+
t.destroy();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Trigger } from "w3ts";
|
|
2
|
+
import { forEachPlayer } from "./players";
|
|
3
|
+
|
|
4
|
+
type CameraDistances = "mid" | "far" | "max";
|
|
5
|
+
|
|
6
|
+
export function enableCameraZoom() {
|
|
7
|
+
const t = Trigger.create();
|
|
8
|
+
forEachPlayer((p) => {
|
|
9
|
+
SetCameraFieldForPlayer(p.handle, CAMERA_FIELD_FARZ, 10000, 0.25);
|
|
10
|
+
|
|
11
|
+
t.registerPlayerChatEvent(p, "-cam", false);
|
|
12
|
+
|
|
13
|
+
t.addAction(() => {
|
|
14
|
+
const str = GetEventPlayerChatString();
|
|
15
|
+
const triggeringPlayer = GetTriggerPlayer();
|
|
16
|
+
if (str && triggeringPlayer) {
|
|
17
|
+
const [command, distance] = str?.split(" ");
|
|
18
|
+
SetCameraFieldForPlayer(
|
|
19
|
+
triggeringPlayer,
|
|
20
|
+
CAMERA_FIELD_FARZ,
|
|
21
|
+
10000,
|
|
22
|
+
0.25
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
if ((distance as CameraDistances) === "mid") {
|
|
26
|
+
SetCameraFieldForPlayer(
|
|
27
|
+
triggeringPlayer,
|
|
28
|
+
CAMERA_FIELD_TARGET_DISTANCE,
|
|
29
|
+
4500,
|
|
30
|
+
0.25
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
} else if ((distance as CameraDistances) === "far") {
|
|
34
|
+
SetCameraFieldForPlayer(
|
|
35
|
+
triggeringPlayer,
|
|
36
|
+
CAMERA_FIELD_TARGET_DISTANCE,
|
|
37
|
+
6000,
|
|
38
|
+
0.25
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
} else if ((distance as CameraDistances) === "max") {
|
|
42
|
+
SetCameraFieldForPlayer(
|
|
43
|
+
triggeringPlayer,
|
|
44
|
+
CAMERA_FIELD_TARGET_DISTANCE,
|
|
45
|
+
7800,
|
|
46
|
+
0.25
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const distanceAsNumber = Number(distance);
|
|
52
|
+
if (typeof distanceAsNumber !== "number") return;
|
|
53
|
+
|
|
54
|
+
SetCameraFieldForPlayer(
|
|
55
|
+
triggeringPlayer,
|
|
56
|
+
CAMERA_FIELD_TARGET_DISTANCE,
|
|
57
|
+
distanceAsNumber,
|
|
58
|
+
0.25
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { MapPlayer, Trigger } from "w3ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Setup trigger to listen for a chat command for the players specified in the array. Execute function when triggered
|
|
5
|
+
* @param command -cam , -name, -start, etc
|
|
6
|
+
* @param players
|
|
7
|
+
* @param fn
|
|
8
|
+
*/
|
|
9
|
+
export function createChatCommand(players: MapPlayer[], command: string, exact: boolean, fn: (player: MapPlayer, data: { trigger: Trigger; command: string; data?: string }) => void) {
|
|
10
|
+
const trigger = Trigger.create();
|
|
11
|
+
|
|
12
|
+
players.forEach((p) => {
|
|
13
|
+
trigger.registerPlayerChatEvent(p, command, exact);
|
|
14
|
+
|
|
15
|
+
trigger.addAction(() => {
|
|
16
|
+
const str = GetEventPlayerChatString();
|
|
17
|
+
const [_, data] = str?.split(" ") ?? [];
|
|
18
|
+
|
|
19
|
+
fn(p, { trigger, command, data });
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export const playerRGBMap = new Map<number, { r: number; g: number; b: number }>([
|
|
2
|
+
[0, { r: 255, g: 0, b: 0 }],
|
|
3
|
+
[1, { r: 0, g: 0, b: 255 }],
|
|
4
|
+
[2, { r: 0, g: 255, b: 255 }],
|
|
5
|
+
[3, { r: 128, g: 0, b: 128 }],
|
|
6
|
+
[4, { r: 255, g: 255, b: 0 }],
|
|
7
|
+
[5, { r: 254, g: 137, b: 13 }],
|
|
8
|
+
[6, { r: 32, g: 192, b: 0 }],
|
|
9
|
+
[7, { r: 229, g: 91, b: 176 }],
|
|
10
|
+
[8, { r: 149, g: 150, b: 151 }],
|
|
11
|
+
[9, { r: 126, g: 191, b: 241 }],
|
|
12
|
+
[10, { r: 16, g: 98, b: 70 }],
|
|
13
|
+
[11, { r: 78, g: 42, b: 4 }],
|
|
14
|
+
[12, { r: 155, g: 0, b: 0 }],
|
|
15
|
+
[13, { r: 0, g: 0, b: 195 }],
|
|
16
|
+
[14, { r: 155, g: 234, b: 255 }],
|
|
17
|
+
[15, { r: 190, g: 0, b: 254 }],
|
|
18
|
+
[16, { r: 235, g: 205, b: 135 }],
|
|
19
|
+
[17, { r: 248, g: 164, b: 139 }],
|
|
20
|
+
[18, { r: 191, g: 255, b: 128 }],
|
|
21
|
+
[19, { r: 220, g: 185, b: 235 }],
|
|
22
|
+
[20, { r: 40, g: 40, b: 40 }],
|
|
23
|
+
[21, { r: 235, g: 240, b: 255 }],
|
|
24
|
+
[22, { r: 0, g: 120, b: 30 }],
|
|
25
|
+
[23, { r: 164, g: 111, b: 51 }],
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export const playerHexColorMap = new Map<number, string>([
|
|
29
|
+
[0, "ff0303"],
|
|
30
|
+
[1, "0042ff"],
|
|
31
|
+
[2, "1ce6b9"],
|
|
32
|
+
[3, "540081"],
|
|
33
|
+
[4, "fffc00"],
|
|
34
|
+
[5, "fe8a0e"],
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
*
|
|
40
|
+
PlayerStrings[0].colour = "|c00ff0303"
|
|
41
|
+
PlayerStrings[1].colour = "|c000042ff"
|
|
42
|
+
PlayerStrings[2].colour = "|c001ce6b9"
|
|
43
|
+
PlayerStrings[3].colour = "|c00540081"
|
|
44
|
+
PlayerStrings[4].colour = "|c00fffc00"
|
|
45
|
+
PlayerStrings[5].colour = "|c00fe8a0e"
|
|
46
|
+
PlayerStrings[6].colour = "|c0020c000"
|
|
47
|
+
PlayerStrings[7].colour = "|c00e55bb0"
|
|
48
|
+
PlayerStrings[8].colour = "|c00959697"
|
|
49
|
+
PlayerStrings[9].colour = "|c007ebff1"
|
|
50
|
+
PlayerStrings[10].colour = "|c00106246"
|
|
51
|
+
PlayerStrings[11].colour = "|c004e2a04"
|
|
52
|
+
PlayerStrings[12].colour = "|c009b0000"
|
|
53
|
+
PlayerStrings[13].colour = "|c000000c3"
|
|
54
|
+
PlayerStrings[14].colour = "|c0000eaff"
|
|
55
|
+
PlayerStrings[15].colour = "|c00be00fe"
|
|
56
|
+
PlayerStrings[16].colour = "|c00ebcd87"
|
|
57
|
+
PlayerStrings[17].colour = "|c00f8a48b"
|
|
58
|
+
PlayerStrings[18].colour = "|c00bfff80"
|
|
59
|
+
PlayerStrings[19].colour = "|c00dcb9eb"
|
|
60
|
+
PlayerStrings[20].colour = "|c00282828"
|
|
61
|
+
PlayerStrings[21].colour = "|c00ebf0ff"
|
|
62
|
+
PlayerStrings[22].colour = "|c0000781e"
|
|
63
|
+
PlayerStrings[23].colour = "|c00a46f33"
|
|
64
|
+
-- RGB
|
|
65
|
+
PlayerStrings[0].red = 255
|
|
66
|
+
PlayerStrings[0].green = 3
|
|
67
|
+
PlayerStrings[0].blue = 3
|
|
68
|
+
PlayerStrings[1].red = 0
|
|
69
|
+
PlayerStrings[1].green = 66
|
|
70
|
+
PlayerStrings[1].blue = 255
|
|
71
|
+
PlayerStrings[2].red = 28
|
|
72
|
+
PlayerStrings[2].green = 230
|
|
73
|
+
PlayerStrings[2].blue = 185
|
|
74
|
+
PlayerStrings[3].red = 84
|
|
75
|
+
PlayerStrings[3].green = 0
|
|
76
|
+
PlayerStrings[3].blue = 129
|
|
77
|
+
PlayerStrings[4].red = 255
|
|
78
|
+
PlayerStrings[4].green = 254
|
|
79
|
+
PlayerStrings[4].blue = 0
|
|
80
|
+
PlayerStrings[5].red = 254
|
|
81
|
+
PlayerStrings[5].green = 138
|
|
82
|
+
PlayerStrings[5].blue = 14
|
|
83
|
+
PlayerStrings[6].red = 32
|
|
84
|
+
PlayerStrings[6].green = 192
|
|
85
|
+
PlayerStrings[6].blue = 0
|
|
86
|
+
PlayerStrings[7].red = 229
|
|
87
|
+
PlayerStrings[7].green = 91
|
|
88
|
+
PlayerStrings[7].blue = 176
|
|
89
|
+
PlayerStrings[8].red = 149
|
|
90
|
+
PlayerStrings[8].green = 150
|
|
91
|
+
PlayerStrings[8].blue = 151
|
|
92
|
+
PlayerStrings[9].red = 126
|
|
93
|
+
PlayerStrings[9].green = 191
|
|
94
|
+
PlayerStrings[9].blue = 241
|
|
95
|
+
PlayerStrings[10].red = 16
|
|
96
|
+
PlayerStrings[10].green = 98
|
|
97
|
+
PlayerStrings[10].blue = 70
|
|
98
|
+
PlayerStrings[11].red = 78
|
|
99
|
+
PlayerStrings[11].green = 42
|
|
100
|
+
PlayerStrings[11].blue = 4
|
|
101
|
+
PlayerStrings[12].red = 155
|
|
102
|
+
PlayerStrings[12].green = 0
|
|
103
|
+
PlayerStrings[12].blue = 0
|
|
104
|
+
PlayerStrings[13].red = 0
|
|
105
|
+
PlayerStrings[13].green = 0
|
|
106
|
+
PlayerStrings[13].blue = 195
|
|
107
|
+
PlayerStrings[14].red = 155
|
|
108
|
+
PlayerStrings[14].green = 234
|
|
109
|
+
PlayerStrings[14].blue = 255
|
|
110
|
+
PlayerStrings[15].red = 190
|
|
111
|
+
PlayerStrings[15].green = 0
|
|
112
|
+
PlayerStrings[15].blue = 254
|
|
113
|
+
PlayerStrings[16].red = 235
|
|
114
|
+
PlayerStrings[16].green = 205
|
|
115
|
+
PlayerStrings[16].blue = 135
|
|
116
|
+
PlayerStrings[17].red = 248
|
|
117
|
+
PlayerStrings[17].green = 164
|
|
118
|
+
PlayerStrings[17].blue = 139
|
|
119
|
+
PlayerStrings[18].red = 191
|
|
120
|
+
PlayerStrings[18].green = 255
|
|
121
|
+
PlayerStrings[18].blue = 128
|
|
122
|
+
PlayerStrings[19].red = 220
|
|
123
|
+
PlayerStrings[19].green = 185
|
|
124
|
+
PlayerStrings[19].blue = 235
|
|
125
|
+
PlayerStrings[20].red = 40
|
|
126
|
+
PlayerStrings[20].green = 40
|
|
127
|
+
PlayerStrings[20].blue = 40
|
|
128
|
+
PlayerStrings[21].red = 235
|
|
129
|
+
PlayerStrings[21].green = 240
|
|
130
|
+
PlayerStrings[21].blue = 255
|
|
131
|
+
PlayerStrings[22].red = 0
|
|
132
|
+
PlayerStrings[22].green = 120
|
|
133
|
+
PlayerStrings[22].blue = 30
|
|
134
|
+
PlayerStrings[23].red = 164
|
|
135
|
+
PlayerStrings[23].green = 111
|
|
136
|
+
PlayerStrings[23].blue = 51
|
|
137
|
+
*
|
|
138
|
+
*
|
|
139
|
+
*/
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Effect, Item, Trigger, Unit } from "w3ts";
|
|
2
|
+
import { notifyPlayer, useTempEffect } from "./misc";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Checks if the unit has an item in their item slots
|
|
6
|
+
* @param u
|
|
7
|
+
* @param itemTypeId
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
export function unitHasItem(u: Unit, itemTypeId: number): boolean {
|
|
11
|
+
for (let x = 0; x < 6; x++) {
|
|
12
|
+
if (u.getItemInSlot(x)?.typeId === itemTypeId) {
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Player should pick up recipes when needed. if they are missing items then the recipe cost is refunded
|
|
21
|
+
export function trig_itemRecipeSystem() {
|
|
22
|
+
//takes a set of items
|
|
23
|
+
//unit or unit clicks
|
|
24
|
+
const t = Trigger.create();
|
|
25
|
+
|
|
26
|
+
t.registerAnyUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM);
|
|
27
|
+
t.addAction(() => {
|
|
28
|
+
const recipeItem = Item.fromEvent();
|
|
29
|
+
const unit = Unit.fromHandle(GetTriggerUnit());
|
|
30
|
+
|
|
31
|
+
if (!unit || !recipeItem) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//for every item they have, if any has the word recipe, then we check to see if they have the items needed to satisfy the recipe
|
|
36
|
+
for (let x = 0; x < 6; x++) {
|
|
37
|
+
const currItem = unit?.getItemInSlot(x);
|
|
38
|
+
if (currItem?.name.toLowerCase().includes("recipe")) {
|
|
39
|
+
checkItemRecipeRequirements(unit, currItem);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkItemRecipeRequirements(unit: Unit, recipeItem: Item) {
|
|
46
|
+
if (recipeItem.name.toLowerCase().includes("recipe")) {
|
|
47
|
+
let requiredItems: RecipeItemRequirement[] | null = null;
|
|
48
|
+
let itemToCreateId: number | null = null;
|
|
49
|
+
|
|
50
|
+
for (const [key, value] of itemRecipesMap.entries()) {
|
|
51
|
+
if (key.recipeId === recipeItem.typeId) {
|
|
52
|
+
requiredItems = value;
|
|
53
|
+
itemToCreateId = key.itemId;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (!requiredItems) {
|
|
58
|
+
print("Missing required items data for the recipe ", recipeItem.name);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* unique list of item types and their quantity and charges that we found on the unit
|
|
64
|
+
*/
|
|
65
|
+
const matchingItems: RecipeItemRequirement[] = [];
|
|
66
|
+
|
|
67
|
+
//Loop through the units item slots
|
|
68
|
+
for (let x = 0; x < 6; x++) {
|
|
69
|
+
const currItem = unit?.getItemInSlot(x);
|
|
70
|
+
|
|
71
|
+
//Check that the current item matches at least one of the item types in the requirement list
|
|
72
|
+
if (currItem && requiredItems.some((req) => req.itemTypeId === currItem.typeId)) {
|
|
73
|
+
const alreadyStoredItemIndex = matchingItems.findIndex((itemReq) => itemReq.itemTypeId === currItem.typeId);
|
|
74
|
+
|
|
75
|
+
//If we already came across this item in the unit inventory then increment the quantity
|
|
76
|
+
if (alreadyStoredItemIndex && matchingItems[alreadyStoredItemIndex]) {
|
|
77
|
+
matchingItems[alreadyStoredItemIndex].quantity++;
|
|
78
|
+
}
|
|
79
|
+
//otherwise it is our first match with this item type, so add it to our array of matching items
|
|
80
|
+
else {
|
|
81
|
+
matchingItems.push({ itemTypeId: currItem.typeId, quantity: 1, charges: 0 });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
//Check that every item required is found in the units inventory satisfies the quantity requirement for the recipe
|
|
87
|
+
const satisfiesRecipe =
|
|
88
|
+
requiredItems.every((reqItemData) => {
|
|
89
|
+
const matching = matchingItems?.find((matchingItemData) => matchingItemData.itemTypeId === reqItemData.itemTypeId);
|
|
90
|
+
|
|
91
|
+
if (matching) {
|
|
92
|
+
return matching.quantity >= reqItemData.quantity;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return false;
|
|
96
|
+
}) && matchingItems.length > 0;
|
|
97
|
+
|
|
98
|
+
if (!itemToCreateId) {
|
|
99
|
+
print("Missing the item type id of the item to create for this recipe: ", recipeItem.name);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
//destroys more items than it needds to
|
|
103
|
+
if (satisfiesRecipe && itemToCreateId) {
|
|
104
|
+
//add the item
|
|
105
|
+
requiredItems.forEach((req: RecipeItemRequirement) => {
|
|
106
|
+
for (let x = 0; x < req.quantity; x++) {
|
|
107
|
+
for (let x = 0; x < 6; x++) {
|
|
108
|
+
const currItem = unit?.getItemInSlot(x);
|
|
109
|
+
if (currItem?.typeId === req.itemTypeId) {
|
|
110
|
+
currItem?.destroy();
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
recipeItem.destroy();
|
|
118
|
+
|
|
119
|
+
unit.addItemById(itemToCreateId);
|
|
120
|
+
|
|
121
|
+
useTempEffect(Effect.create("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", unit.x, unit.y));
|
|
122
|
+
const clap = Effect.create("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", unit.x, unit.y);
|
|
123
|
+
clap?.setScaleMatrix(0.5, 0.5, 0.5);
|
|
124
|
+
useTempEffect(clap);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!satisfiesRecipe) {
|
|
128
|
+
//refund the gold
|
|
129
|
+
notifyPlayer(`Missing recipe requirements for: ${recipeItem.name}.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
//use if or rf to store item gold cost in the world editor
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
interface RecipeItemRequirement {
|
|
137
|
+
itemTypeId: number; //ITEMS;
|
|
138
|
+
quantity: number;
|
|
139
|
+
charges: number;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
interface RecipeItem {
|
|
143
|
+
recipeId: number;
|
|
144
|
+
itemId: number;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const itemRecipesMap = new Map<RecipeItem, RecipeItemRequirement[]>([
|
|
148
|
+
// [
|
|
149
|
+
// { recipeId: ITEMS.recipe_blinkTreads, itemId: ITEMS.blinkTreads },
|
|
150
|
+
// [
|
|
151
|
+
// { itemTypeId: ITEMS.bootsOfSpeed, quantity: 1, charges: 0 }, //
|
|
152
|
+
// { itemTypeId: ITEMS.blinkDagger, quantity: 1, charges: 0 }, //
|
|
153
|
+
// ],
|
|
154
|
+
// ],
|
|
155
|
+
]);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated
|
|
3
|
+
* @param a
|
|
4
|
+
* @param b
|
|
5
|
+
* @returns
|
|
6
|
+
*/
|
|
7
|
+
export function distanceBetweenCoords(a: { x: number; y: number }, b: { x: number; y: number }) {
|
|
8
|
+
const deltaX = a.x - b.x;
|
|
9
|
+
const deltaY = a.y - b.y;
|
|
10
|
+
const squaredDist = Math.pow(deltaX, 2) + Math.pow(deltaY, 2);
|
|
11
|
+
const dist = Math.sqrt(squaredDist);
|
|
12
|
+
|
|
13
|
+
return dist;
|
|
14
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type MINIMAP_ICONS =
|
|
2
|
+
| "UI\\Minimap\\MiniMap-ControlPoint.mdl"
|
|
3
|
+
| "UI\\Minimap\\MiniMap-QuestGiver.mdl"
|
|
4
|
+
| "UI\\Minimap\\Minimap-QuestObjectiveBonus.mdl"
|
|
5
|
+
| "UI\\Minimap\\Minimap-QuestObjectivePrimary.mdl"
|
|
6
|
+
| "UI\\Minimap\\Minimap-QuestTurnIn.mdl"
|
|
7
|
+
| "UI\\Minimap\\MiniMap-Hero.mdl"
|
|
8
|
+
| "UI\\Minimap\\Minimap-Ping.mdl"
|
|
9
|
+
| "UI\\Minimap\\MiniMap-Item.mdl"
|
|
10
|
+
| "UI\\Minimap\\MiniMap-NeutralBuilding.mdl";
|
|
11
|
+
|
|
12
|
+
export enum MinimapIconPath {
|
|
13
|
+
controlPoint = "UI\\Minimap\\MiniMap-ControlPoint.mdl",
|
|
14
|
+
questGiver = "UI\\Minimap\\MiniMap-QuestGiver.mdl",
|
|
15
|
+
questObjectiveBonus = "UI\\Minimap\\Minimap-QuestObjectiveBonus.mdl",
|
|
16
|
+
questObjectivePrimary = "UI\\Minimap\\Minimap-QuestObjectivePrimary.mdl",
|
|
17
|
+
questTurnIn = "UI\\Minimap\\Minimap-QuestTurnIn.mdl",
|
|
18
|
+
hero = "UI\\Minimap\\MiniMap-Hero.mdl",
|
|
19
|
+
item = "UI\\Minimap\\MiniMap-Item.mdl",
|
|
20
|
+
neutralBuilding = "UI\\Minimap\\MiniMap-NeutralBuilding.mdl",
|
|
21
|
+
ping = "UI\\Minimap\\Minimap-Ping.mdl",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const minimapIconPathsSet = new Set<MINIMAP_ICONS>([
|
|
25
|
+
"UI\\Minimap\\MiniMap-ControlPoint.mdl",
|
|
26
|
+
"UI\\Minimap\\MiniMap-Hero.mdl",
|
|
27
|
+
"UI\\Minimap\\MiniMap-Item.mdl",
|
|
28
|
+
"UI\\Minimap\\MiniMap-NeutralBuilding.mdl",
|
|
29
|
+
"UI\\Minimap\\MiniMap-QuestGiver.mdl",
|
|
30
|
+
"UI\\Minimap\\Minimap-Ping.mdl",
|
|
31
|
+
"UI\\Minimap\\Minimap-QuestObjectiveBonus.mdl",
|
|
32
|
+
"UI\\Minimap\\Minimap-QuestObjectivePrimary.mdl",
|
|
33
|
+
"UI\\Minimap\\Minimap-QuestTurnIn.mdl",
|
|
34
|
+
]);
|