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
|
// TMW 2 Script
// Author:
// Jesusalva
// Micksha
// Description:
// Controls sewers.
// FIXME: The warps back should only work if treasure was found
// Spawn monsters and respawns them.
// A simple random treasure chest - to be sure players were introduced to this
// awesome system. Same rules as any treasure box still applies.
042-5,0,0,0 script #ctrl0425 NPC_HIDDEN,{
function monster0425;
end;
OnInstanceInit:
// Yes, we just hope it works out of box
explode(.@map$, .map$, "@");
.@g=atoi(.@map$[1]);
if (.@g < 1) {
debugmes "[ERROR] [KAMELOT] Unable to spawn for Kamelot %s", .map$;
debugmes "[ERROR] [KAMELOT] Using dummy data (returned: %d)", .@g;
.@g=0;
}
debugmes "Spawning monsters for guild %d", .@g;
.@mx=getguildavg(.@g);
monster0425(4, 35, 20, 60, 100, .@mx);
monster0425(4, 20, 20, 35, 100, .@mx);
// Neutral monsters
areamonster(.map$, 35, 20, 60, 100, strmobinfo(1, Blub), Blub, 5);
areamonster(.map$, 20, 20, 35, 100, strmobinfo(1, ManaGhost), ManaGhost, max(1, .@mx/10));
// Bonus monsters
if (!rand2(3))
areamonster(.map$, 20, 20, 60, 100, strmobinfo(1, WhirlyBird), WhirlyBird, 1);
if (!rand2(2))
areamonster(.map$, 20, 20, 60, 100, strmobinfo(1, SilverChest), SilverChest, 1);
if (!rand2(2))
areamonster(.map$, 20, 20, 60, 100, strmobinfo(1, BronzeChest), BronzeChest, 2);
end;
OnKillMob:
if (!playerattached())
goto OnRespawn;
// Maybe a reward is due
.@g=getcharid(2);
if (.@g < 1) die();
getexp $KAMELOT_MX[.@g]*7, $KAMELOT_MX[.@g]*3;
.@delay=max(7000, 42000-$KAMELOT_PC[.@g]*2000);
// FALLTHROUGH
OnRespawn:
.@delay=(.@delay ? .@delay : 7000);
sleep(.@delay);
// Yes, we just hope it works out of box
explode(.@map$, .map$, "@");
.@g=atoi(.@map$[1]);
if (.@g < 1) {
debugmes "[ERROR] [KAMELOT] Unable to respawn for Kamelot %s", .map$;
.@g=0;
}
monster0425(1, 20, 20, 80, 120, $KAMELOT_MX[.@g]);
end;
function monster0425 {
.@label$=instance_npcname(.name$)+"::OnKillMob";
.@gcount=getarg(0);
.@x1=getarg(1);
.@y1=getarg(2);
.@x2=getarg(3);
.@y2=getarg(4);
.@avg=getarg(5);
.@m$=instance_mapname("042-5");
//debugmes "Total %d, map %s (power %d)", .@gcount, .@m$, .@avg;
freeloop(true);
for (.@i=0; .@i < .@gcount; .@i++) {
.@mobId=any(CursedSoldier, CursedArcher); // 50-50 ratio
.@mob=areamonster(.@m$, .@x1, .@y1, .@x2, .@y2, strmobinfo(1, .@mobId), .@mobId, 1, .@label$);
// Reconfigure the monster
setunitdata(.@mob, UDT_LEVEL, .@avg);
setunitdata(.@mob, UDT_STR, 1+.@avg*5/10);
setunitdata(.@mob, UDT_AGI, 1+.@avg*4/10);
setunitdata(.@mob, UDT_VIT, 1+.@avg*5/10);
setunitdata(.@mob, UDT_INT, 1+.@avg*5/10);
setunitdata(.@mob, UDT_DEX, 1+.@avg*5/10);
setunitdata(.@mob, UDT_LUK, 1+.@avg*4/10);
setunitdata(.@mob, UDT_ADELAY, 1472);
setunitdata(.@mob, UDT_ATKRANGE, (.@mobId == CursedArcher ? any(6,7) : any(1,2)));
// Battle Status
setunitdata(.@mob, UDT_MAXHP, .@avg*38);
setunitdata(.@mob, UDT_HP, .@avg*38);
setunitdata(.@mob, UDT_ATKMIN, .@avg*50/10);
setunitdata(.@mob, UDT_ATKMAX, .@avg*70/10);
setunitdata(.@mob, UDT_DEF, 1+.@avg*11/10);
setunitdata(.@mob, UDT_MDEF, 1+.@avg*7/10);
setunitdata(.@mob, UDT_HIT, .@avg*6); // Advised: x3
setunitdata(.@mob, UDT_FLEE, .@avg*40/10); // Advised: x4
// Critical calculation
.@min=8;
.@max=max(.@min, min(35, .@avg/4));
setunitdata(.@mob, UDT_CRIT, rand2(.@min, .@max));
// Loop through
}
freeloop(false);
return;
}
}
///////////////////////////////////////////////////////////////
// This is required for others
// KamelotTreasure( POSITION ID )
function script KamelotTreasure {
.@id=getarg(0);
.@g=getcharid(2);
if (.@g < 1) die();
if ($KAMELOT_KEYMASK[.@g] & .@id) {
mesc l("The chest is unlocked and empty.");
close;
}
inventoryplace Iten, 1, NPCEyes, 2;
mesc l("Open the chest?");
mesc l("Cost: 1 @@", getitemlink(TreasureKey)), 1;
if (!countitem(TreasureKey))
close;
next;
if (askyesno() == ASK_NO)
close;
delitem TreasureKey, 1;
mesc l("You open the chest!");
.@empty=($KAMELOT_KEYMASK[.@g] & .@id);
$KAMELOT_KEYMASK[.@g]=$KAMELOT_KEYMASK[.@g]|.@id;
if (!.@empty) {
if (.@id == $KAMELOT_KEY[.@g]) {
dispbottom l("You found the key!");
rentitem KamelotKey, 86400; // Ensure they expire after 24 hours
.@key=true;
}
.@r=rand2(10000)-$KAMELOT_MX[.@g]+100;
// Select treasure list
if (.@r <= 0) // 0.01% total, 0.025% each
.@loot=any(MylarinDust, SupremeGift, HousingLetterIII, TimeFlask, MercCard_EH);
else if (.@r < 300) // 0.3% each
.@loot=any(MagicApple, PrismGift, EquipmentBlueprintD, ChampionshipBow, Halberd, AncientShield, AncientSword, Setzer, MercBoxD, Shemagh);
else
.@loot=any(SacredImmortalityPotion, DivineApple, ArcmageBoxset, GoldenApple, MercBoxA, MercBoxB, MercBoxC, MoveSpeedPotion, AtroposMixture, EverburnPowder, IridiumOre, PlatinumOre, YerbaMate, SmokeGrenade, SnakeEgg, LachesisBrew, BoneAmmoBox, GoldPieces, SilverGift, TerraniteOre, LeadOre, TinOre, SilverOre, GoldOre, TitaniumOre, FluoPowder, EquipmentBlueprintC, AlchemyBlueprintC, AlchemyBlueprintD, AncientBlueprint, JasmineTea, MoubooSteak, ClothoLiquor, Coal, RedPlushWine, HastePotion, CoinBag, StrengthPotion, Pearl, BronzeGift, IronOre, CopperOre, BlueDye, EquipmentBlueprintB, AlchemyBlueprintB, AlchemyBlueprintC, OolongTea, Croconut, CelestiaTea, MoubooSteak, ClothoLiquor, Coal, SmallMushroom, HastePotion, StrengthPotion, WoodenLog, LeatherPatch, DwarvenSake, EquipmentBlueprintA, EquipmentBlueprintB, AlchemyBlueprintA, SpearmintTea, TreasureMap, FatesPotion, CrazyRum, LightGreenDiamond, EarthPowder, WoodenLog, MysteriousBottle, FluoPowder, ChamomileTea); // > 70 options (~1% each)
mesc l("You find @@ inside!", getitemlink(.@loot));
// If itemtype == Armor/Weapon, make it guild bound and put bonus
.@t=getiteminfo(.@loot, ITEMINFO_TYPE);
if (.@t == IT_WEAPON) {
getitembound .@loot, 1, 2;
delinventorylist(); // Needed, because we'll rely on rfind()
getinventorylist();
.@index=array_rfind(@inventorylist_id, .@loot);
setitemoptionbyindex(.@index, 0, IOPT_SPLASHDAMAGE, 1);
} else if (.@t == IT_ARMOR) {
getitembound .@loot, 1, 2;
delinventorylist(); // Needed, because we'll rely on rfind()
getinventorylist();
.@index=array_rfind(@inventorylist_id, .@loot);
setitemoptionbyindex(.@index, 0, ATTR_TOLERACE_ALL, 10);
} else {
getitem .@loot, 1;
}
} else {
mesc l("You find @@ inside!", l("nothing"));
mesc l("Seems like someone else opened this chest before you!");
}
// Announcement
if (.@key)
.@p$=b(" They found the key!");
mapannounce "042-6@"+.@g, strcharinfo(0)+" has opened a Treasure Chest!"+.@p$, 0;
mapannounce "042-7@"+.@g, strcharinfo(0)+" has opened a Treasure Chest!"+.@p$, 0;
mapannounce "042-8@"+.@g, strcharinfo(0)+" has opened a Treasure Chest!"+.@p$, 0;
mapannounce "042-9@"+.@g, strcharinfo(0)+" has opened a Treasure Chest!"+.@p$, 0;
// Guild Master Notification
.@gm$=getguildmaster(.@g);
if (!getcharid(3, .@gm$)) return;
.@gma=getcharid(3, .@gm$);
.@gmb=getcharid(0, .@gm$);
if (!isloggedin(.@gma, .@gmb)) return;
message .@gm$, strcharinfo(0)+" has opened a Treasure Chest."+.@p$;
return;
}
/////////////////////////////////////////////////////////////////////////////
// KamelotBoss(Map, x, y, power, NPC)
function script KamelotBoss {
.@label$=instance_npcname(getarg(4))+"::OnKillBoss";
.@gcount=1;
.@x1=getarg(1);
.@y1=getarg(2);
.@avg=getarg(3);
.@m$=instance_mapname(getarg(0));
//debugmes "Total %d, map %s (power %d)", .@gcount, .@m$, .@avg;
.@mobId=any(CursedSoldier, CursedArcher); // 50-50 ratio
.@name$=any("Gawain", "Tristan", "Kay", "Palamedes", "Mordred", "Bors", "Bedivere", "Lionel", "Bleoberry", "Lucan", "Lamorak", "Pellas", "Hector", "Dragonet", "Bronor", "Alymere"); // Typos on purpose: https://www.arthurian-legend.com/knights-round-table/
.@mob=monster(.@m$, .@x1, .@y1, .@name$, .@mobId, 1, .@label$);
// Reconfigure the monster
setunitdata(.@mob, UDT_LEVEL, .@avg+20);
setunitdata(.@mob, UDT_STR, 1+.@avg*7/10);
setunitdata(.@mob, UDT_AGI, 1+.@avg*5/10);
setunitdata(.@mob, UDT_VIT, 1+.@avg*7/10);
setunitdata(.@mob, UDT_INT, 1+.@avg*6/10);
setunitdata(.@mob, UDT_DEX, 1+.@avg*6/10);
setunitdata(.@mob, UDT_LUK, 1+.@avg*7/10);
setunitdata(.@mob, UDT_ADELAY, 1072);
setunitdata(.@mob, UDT_ATKRANGE, (.@mobId == CursedArcher ? any(7,8) : any(2,2,3)));
// Battle Status
setunitdata(.@mob, UDT_MAXHP, .@avg*450);
setunitdata(.@mob, UDT_HP, .@avg*450);
setunitdata(.@mob, UDT_ATKMIN, .@avg*60/10);
setunitdata(.@mob, UDT_ATKMAX, .@avg*70/10);
setunitdata(.@mob, UDT_DEF, 1+.@avg*14/10);
setunitdata(.@mob, UDT_MDEF, 1+.@avg*9/10);
setunitdata(.@mob, UDT_HIT, .@avg*16); // Advised: x3
setunitdata(.@mob, UDT_FLEE, .@avg*45/10); // Advised: x4
// Critical calculation
.@min=30;
.@max=max(.@min, min(70, .@avg/2));
setunitdata(.@mob, UDT_CRIT, rand2(.@min, .@max));
return;
}
|