// TMW2 scripts.
// Authors:
// Jesusalva
// dangerDuck
// TMW Org.
// Description:
// HUB functions (Login, Logout, Death)
// HUB_Login ()
function script HUB_Login {
getmapxy(.@mapa$, .@a,.@b, 0);
// Login on Blue Sage Workshop/Library
if (.@mapa$ == "020-7-1") {
addtimer(1000, "#BlueSageHUB::OnCycle");
}
// Random Treasure cleaning
if (CHAREG_CLEANUP < gettimetick(2)-CHEST_WAITTIME)
deletearray RNGTREASURE_DATE;
// PVP Cooldown cleaning
if (CHAREG_CLEANUP < gettimetick(2)-PVP_WAITTIME)
deletearray PVP_COOLDOWN;
// Mirror Lake functionality
if (getvaultid() && !getstatus(SC_JAILED)) {
// Christmas 2021
if ($EVENT$ == "Christmas") {
if (#XMAS2021 < ##01_TMWEXP) {
getexp (##01_TMWEXP-#XMAS2021) * 7 / 10, 0;
#XMAS2021 = ##01_TMWEXP;
} else if (#XMAS2021 > ##01_TMWEXP) {
// You leveled up?
getexp ##01_TMWEXP * 7 / 10, 0;
#XMAS2021 = ##01_TMWEXP;
}
}
.@gto=get_byte(##00_INFO, 3);
.@mlp=get_nibble(##00_INFO, 5);
/*debugmes "INFO: %d", ##00_INFO;
debugmes "BYTE: %d/%d/%d/%d", get_byte(##00_INFO, 0), get_byte(##00_INFO, 1), get_byte(##00_INFO, 2), get_byte(##00_INFO, 3);
debugmes "NIBBLE: %d/%d/%d/%d/%d/%d/%d/%d", get_nibble(##00_INFO, 0), get_nibble(##00_INFO, 1), get_nibble(##00_INFO, 2), get_nibble(##00_INFO, 3), get_nibble(##00_INFO, 4), get_nibble(##00_INFO, 5), get_nibble(##00_INFO, 6), get_nibble(##00_INFO, 7);
debugmes "Your Vault ID: %d", getvaultid();
debugmes "VAULT LOGIN, GTO is %d MLP is %d", .@gto, .@mlp;
*/
// Work only on new chars, or chars which cleared Tulimshar.
if (.@gto == WORLD_ID &&
(!getq(General_Narrator) ||
(getq(General_Narrator) && BaseLevel > 20)))
{
// Warp to the proper Mirror Lake
switch (.@mlp) {
case 1: warp "018-7-1", 90, 47; LOCATION$ = "LoF"; break;
default: warp "014-4", 28, 31; LOCATION$ = "LoF"; break;
}
// Send debug information
debugmes("Vault User %d moved to lake %d.", getvaultid(), .@mlp);
// Handle new user (non-native) accounts
if (!getq(General_Narrator)) {
adddefaultskills();
getitembound MirrorLakeArmor, 1, 4;
equip(MirrorLakeArmor);
percentheal(100, 100);
TUTORIAL=true;
//BaseLevel = get_byte(##00_INFO, 0) + 1;
// TODO: Display quick tutorial
dispbottom l("Mirror Lake : Created temporary character; It'll be reset on logout.");
dispbottom l("Mirror Lake : Obtain help with %s.", b("@info"));
}
// Unset the target lake/world
set_byte(##00_INFO, 3, 0);
set_nibble(##00_INFO, 5, 0);
} else if (.@gto) {
// Heading somewhere which is not here!
mesc l("WARNING: If you use any Mirror Lake feature on this world, the current Mirror Lake Quest will be marked as \"Failed\"."), 1;
mesc l("If this is undesired, select the correct world, and if needed create a new char on it."), 1;
##VAULT_GOTO=.@gto;
##VAULT_MLTO=.@mlp;
next;
closeclientdialog;
}
}
return;
}
// HUB_Logout ( {dead} )
function script HUB_Logout {
.@dead=getarg(0, false);
getmapxy(.@mapa$, .@a,.@b, 0);
.@zone$=getmapinfo(MAPINFO_ZONE, .@mapa$);
// Hardcore Server
if ($HARDCORE && .@dead) {
// Update Absolutions
if (ABSOLVE_DAY != gettimeparam(GETTIME_DAYOFMONTH)) {
ABSOLVE_DAY=gettimeparam(GETTIME_DAYOFMONTH);
ABSOLVE_CNT=0;
}
// Main Loop
if (@grace) {
// Grace is upon you (ie. script death)
@grace=false;
} else if (.@zone$ == "MMO" && ABSOLVE_CNT <= 3) {
// Absolve (limited attempts)
ABSOLVE_CNT+=1;
dispbottom l("This is a special map so your death is not counted.");
dispbottom b(l("You have %d non-counting deaths remaining today.", 3-ABSOLVE_CNT));
if (ABSOLVE_CNT == 3)
dispbottom col(l("WARNING: if you die again today in a special map it will be PERMANENT."), 1);
} else {
// Meet your final demise!
atcommand("@dropall");
makeitem CoinBag, Zeny/500, .@mapa$, .@a, .@b;
Zeny=0;
//resetlvl(2); // FIXME: Split the exp
// TODO: Warp back to Candor or it'll be unplayable
// TODO: It could be @jail, but it is buggy
atcommand("@jailfor 1d "+strcharinfo(0));
}
// Vanished on Cindy Cave
} else if (.@mapa$ == "021-4" && strcharinfo(0) == $@CINDY_HERO$) {
donpcevent("Cindy#Outside::OnReckless");
recovery(getcharid(3));
warp any("010-1", "010-2"), 0, 0;
sc_start2 SC_POISON, 1, 90, 10000;
die();
if (!$HARDCORE) heal -1, -1;
} else if (.@mapa$ == "021-4") {
.@pl = getmapusers("021-4")-1;
if (.@pl < 1)
donpcevent("Cindy#Outside::OnCleanUp");
recovery(getcharid(3));
warp "Save", 0, 0;
}
// Logout while donating blood
if (getq(HurnscaldQuest_BloodDonor) == 2) {
slide 35, 28;
setpcblock(PCBLOCK_SOFT, false);
setq HurnscaldQuest_BloodDonor, 0, gettimetick(2)+3600; // one hour penalty
}
// Logout/Death on Nard's ship hold
if (compare(.@mapa$, "002-2") || compare(.@mapa$,"nard")) {
setq2 ShipQuests_Peter, 0;
setq3 ShipQuests_Peter, 0; // Bugfix
}
// Logout on botcheck area
if (compare(.@mapa$,"botcheck") && !.@dead) {
if (!is_staff()) {
atcommand "@jail "+strcharinfo(0);
rodex_sendmail(getcharid(0), "TMW2 Team", "Error detected!", "Please contact GM Team, error code: BOTCHECK-AUTOJAILED");
}
}
// Died on Terranite Cave where exp penalty is lower
if (.@mapa$ == "015-6" && .@dead) {
@deathpenalty_override=2;
@deathpenalty_realvalue=readparam(BaseExp);
@deathpenalty_realvaljob=readparam(JobExp);
}
// Died or logged out at Tulimshar Arena (FIXME)
if (compare(.@mapa$, "ARENA")) {
if ('UDTf) {
'UDTf=0;
killmonster(getmap(), instance_npcname("Arnea#003-13")+"::OnGladius");
deltimer(instance_npcname("Arnea#003-13")+"::OnVerify");
}
}
// Died or logged out at Candor Battle
if (.@mapa$ == "006-1") {
if (@crazypoints > CRAZYPOINTS) {
CRAZYPOINTS=@crazypoints;
dispbottom l("Crazyfefe Cave: New Highscore: @@ points", CRAZYPOINTS);
@crazypoints=0;
}
}
// Died or logged out at Candor Survival
if (.@mapa$ == "006-9" && $@SURVIVAL_CANDOR) {
.@score = gettimetick(2)-$@SURVIVAL_CANDOR;
if (.@score > SCANDORPTS) {
SCANDORPTS=.@score;
dispbottom l("Candor Survival : New Highscore: %s", FuzzyTime(gettimetick(2)+.@score));
}
warp "005-1", 66, 63;
}
// Died or logged during Bandit Lord fight
if (.@mapa$ == "015-2") {
if (isin("015-2", 228, 227, 282, 280))
killmonster("015-2", "#BanditLordDen::OnLordDeath");
}
// Died or logged out on Blue Sage House
if (.@mapa$ == "020-7-1") {
callfunc("BSClearNest", @nestid);
}
// Died or logged out on Player Story 2 - Magic School Port
if (compare(.@mapa$, "0030")) {
if (@ASSASSIN > 0)
delcells "MQ2Wall"+getcharid(0);
killmonsterall(getmap());
}
// Died or logged out on Player Story 5 - Forgotten Throne Room
if (compare(.@mapa$, "hmc")) {
if (@instid > 0)
.@n$=instance_npcname("#Core02331", @instid);
else
.@n$=instance_npcname("#Core02331");
deltimer(.@n$+"::OnW01");
deltimer(.@n$+"::OnW02");
deltimer(.@n$+"::OnE07");
deltimer(.@n$+"::OnE08");
deltimer(.@n$+"::OnE09");
deltimer(.@n$+"::OnE10");
deltimer(.@n$+"::OnE11");
deltimer(.@n$+"::OnE12");
killmonsterall(getmap());
}
// Died or logged out on Player Story 6 - Forgotten Shrine
if (compare(.@mapa$, "brb3")) {
//.@n$=instance_npcname("#Core02331");
.@q=getq(LoFQuest_Barbara);
// Reset quest to: Forgotten Chamber Puzzle incomplete
if (.@q < 4 && .@q >= 2) {
setq1 LoFQuest_Barbara, 1;
setq3 LoFQuest_Barbara, 1;
// FIXME: Enable Mana Stone#01863
dispbottom col(l("WARNING: You died at Forgotten Shrine and the Shrine defense triggered."), 1);
dispbottom col(l("WARNING: Your progress on the quest was lost!"), 1);
}
}
// Died or logged out during Sagratha Fight
if (compare(.@mapa$, "sgt2")) {
setq1 HurnscaldQuest_Sagratha, 3;
setq3 HurnscaldQuest_Sagratha, 0;
deltimer("#SaggyDungeonCore::OnW01");
deltimer("#SaggyDungeonCore::OnW02");
deltimer("#SaggyDungeonCore::OnW03");
deltimer("#SaggyDungeonCore::OnW04");
if (instance_id() >= 0) {
.@n$=instance_npcname("#SaggyDungeonCore");
deltimer(.@n$+"::OnW01");
deltimer(.@n$+"::OnW02");
deltimer(.@n$+"::OnW03");
deltimer(.@n$+"::OnW04");
}
}
// Died or logged out during a Boss Raid event
if (compare(.@mapa$, "fyrb")) {
doevent "sBossRaid::OnDie";
}
// Died or logged out during Yeti King Fight
if (getq(HurnscaldQuest_Celestia) > 1)
setq HurnscaldQuest_Celestia, 1;
// First death produces a warning message
if (PC_DIE_COUNTER <= 1 && .@dead) {
dispbottom l("Dying outside a town square will cause EXP loss.");
}
// If you were travelling and died/logged out, cleaning is needed
if (@timer_navio_running) {
@timer_navio_running=0;
// Logged out? Correct your position to inside the ship
// Of course, this is messy... But still better than Save Point
if (!.@dead) {
if (compare(.@mapa$, "016-"))
warp "016-1", 28, 27;
else if (compare(.@mapa$, "002-"))
warp "002-1", 55, 40;
else
warp "Save", 0, 0;
}
}
// Crazyfefe hot fix
if (.@dead) {
// It was PK
if (killerrid > 2000000 && killerrid < 2100000) {
// PVP flag was off
/*
if (!getmapflag(.@mapa$, mf_pvp) && !getmapflag(.@mapa$, mf_pvp_noparty) && !getmapflag(.@mapa$, mf_pvpnoguild)) {
recovery(getcharid(3));
warp .@mapa$, .@a, .@b;
percentheal 100, 100;
dispbottom l("REVENGE TIME!");
.@trueid=getcharid(3);
//detachrid();
attachrid(killerrid);
setpcblock(PCBLOCK_SOFT, true);
sc_start SC_WALKSPEED,120000,50;
sc_end SC_CASH_PLUSEXP;
sc_end SC_OVERLAPEXPUP;
sc_start SC_OVERLAPEXPUP, 300000, -20;
dispbottom l("For cowardingly killing in a \"secure\" area, you will be severely punished.");
//Karma+=1;
sc_start SC_STUN, 15000, 1, 10000, SCFLAG_NOAVOID|SCFLAG_FIXEDTICK;
addtimer(15000, "#mobptsys::OnUnlock");
percentheal -88, -100;
detachrid();
attachrid(.@trueid);
}
*/
HONOR+=1;
}
}
// This allows code to override death penalty, just once:
// @deathpenalty_override
// Valid values: 1- No penalty. 2- Halved penalty.
// You must also set: @deathpenalty_realvalue and @deathpenalty_realvaljob
if (@deathpenalty_override && .@dead) {
if (is_staff())
debugmes("Old values: %d %d Current Values: %d %d", @deathpenalty_realvalue, @deathpenalty_realvaljob, readparam(BaseExp), readparam(JobExp));
addtimer(300, "#QuirinoHUB::OnNoPenaltyCommand");
}
// Register logout time
if (!.@dead) {
CHAREG_CLEANUP=gettimetick(2);
// Send updates to Vault API
if (getvaultid()) {
.@api$=json_encode("UID", ##VAULT,
"GID", getcharid(3),
"VAR1N", "MLQUEST",
"VAR1V", ##02_MLQUEST,
"VAR2N", "MLWORLD",
"VAR2V", ##02_MLWORLD,
"VEXP", ##VAULT_EXP,
"GOTO", ##VAULT_GOTO,
"MLTO", ##VAULT_MLTO);
##VAULT_EXP=0;
##VAULT_GOTO=0;
##VAULT_MLTO=0;
api_send(API_FLUSHVAULT, .@api$);
// Destroy temporary characters
if (countitem(MirrorLakeArmor)) {
delitem MirrorLakeArmor, countitem(MirrorLakeArmor);
//clearitem(); // Hm.
resetlvl(2);
resetstatus();
resetskill();
warp "000-0", 22, 24;
debugmes("Vault User %d reset!", getvaultid());
}
}
}
callfunc "02524_Avenge_BlackBox", .@dead;
return;
}
// HUB_SkillInvoke ( )
function script HUB_SkillInvoke {
// Something is... wrong
if (@skillId < 1) {
Exception("ILLEGAL SKILL PASSED TO HUB. It has been compromised, Jim.", RB_DEBUGMES|RB_IRCBROADCAST);
debugmes "Legal Caster: %s", strcharinfo(0);
debugmes "Effective Caster: %d", @skillCaster;
return;
}
// Missing Skill Target - Don't ask me, something went wrong.
if (@skillTarget < 1) {
//@skillTarget = getcharid(3);
consolewarn("Skill %d has no target!", @skillId);
consolewarn("Skill Data - Name \"%s\" Level %d User \"%s\"", getskillname(@skillId), @skillLv, strcharinfo(0));
}
// Homunculus Cycle
/* *********************************************************************** */
//debugmes "Skill caster %d", @skillCaster;
if (@skillCaster != getcharid(3)) {
switch (@skillId) {
case TMW2_SKILLX:
sc_start SC_RICHMANKIM, 180000, @skillLv+rand2(9*@skillLv), 10000, SCFLAG_NONE, @skillTarget;
gethomunexp @skillLv;
break;
case TMW2_HOMUN_HEAL:
.@heal=gethominfo(6)*(5+rand2(@skillLv));
heal .@heal, 0;
harm(@skillCaster, -.@heal, HARM_MISC);
break;
case TMW2_LITTLE_WONDERS:
sc_end SC_POISON;
sc_end SC_CURSE;
sc_end SC_SILENCE;
sc_end SC_CONFUSION;
sc_end SC_BLIND;
sc_start SC_CURSE, rand2(3500, 5000), 1, 10000, SCFLAG_NONE, @skillCaster;
break;
case TMW2_CAPRICE:
.@gid = @skillCaster; // gethominfo(0);
.@ak1 = getunitdata(.@gid, UDT_MATKMIN);
.@ak2 = getunitdata(.@gid, UDT_MATKMAX);
for (.@i=0; .@i < limit(1, @skillLv, 10); .@i++) {
harm(@skillTarget, rand2(.@ak1, .@ak2), HARM_MAGI, any(Ele_Fire, Ele_Water, Ele_Wind, Ele_Earth), .@gid);
sleep2(100);
}
break;
case TMW2_AVOID:
.@t = 44 - (@skillLv * 4);
.@p = 105+(@skillLv * 5);
SC_Bonus(.@t, SC_WALKSPEED, .@p);
sc_start SC_WALKSPEED, .@t * 1000, .@p, 10000, SCFLAG_NOAVOID, @skillCaster;
break;
case TMW2_HDEFENCE:
.@t = 43 - (@skillLv * 3);
.@p = 4+(@skillLv * 6);
SC_Bonus(.@t, SC_INCVIT, .@p);
sc_start SC_INCVIT, .@t * 1000, .@p, 10000, SCFLAG_NOAVOID, @skillCaster;
break;
}
return;
}
/* *********************************************************************** */
// TODO: Detect what was script-cast and what was player-case. Then, readd RB_IRCBROADCAST
// If you can't do this: You can't do this
if (getskilllv(@skillId) < @skillLv && @skillId != BS_GREED) {
// Is this ignorable?
if (@scriptsk) {
@scriptsk=false;
} else {
Exception("System ERROR, HSI."+@skillId+" INVALID CAST (got "+@skillLv+" expected "+getskilllv(@skillId)+", sub-LC."+(getcharid(3)-2000000)+")", RB_DEBUGMES|RB_ISFATAL);
}
}
// You are AFK for over 3 minutes, that's crazy, disregard
if (checkidle() > 180)
return;
// Record to database
skillInvoke[@skillId] = skillInvoke[@skillId] + 1;
// Script-based skills
/* *********************************************************************** */
switch (@skillId) {
case TMW2_FAKESKILL:
atcommand("@refresh");
break;
case TMW2_FAKESKILL2:
CMD_toevent();
break;
case TMW2_CRAFT:
UserCtrlPanel();
break;
case TMW2_OVHFIRE:
SK_OVHFire();
break;
case TMW2_MPREGEN:
SK_mpregen();
break;
case TMW2_STUDY:
SK_study(@skillTarget);
break;
case EVOL_AREA_PROVOKE:
if (@skillTargetX && @skillTargetY)
massprovoke(1+@skillLv, getmap(), @skillTargetX, @skillTargetY);
else
massprovoke(1+@skillLv);
// SC_PROVOKE ?
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_GD_INCALL:
GD_allboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_REGEN:
GD_regenerating();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_DEFUP:
GD_defboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_BATTLEPLAN:
GD_atkboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_ATKUP:
GD_atkboost2();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_CRITUP:
GD_critboost();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GD_AUTOREVIVE:
GD_autorevive();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GDP_MAXPOWER:
SK_maximizepower();
GetManaExp(GD_DEVELOPMENT, 10);
break;
case TMW2_GDP_SPREGEN:
SK_spregen();
GetManaExp(GD_DEVELOPMENT, 10);
break;
// Weapon Overload attack
case TMW2_OVERLOAD:
.@PW=200+(@skillLv > 3 ? @skillLv : 0)+(@skillLv > 7 ? @skillLv*2 : 0);
areaharm(@skillTarget, 0, AdjustAttackpower(.@PW), HARM_MISC);
break;
////////////////////////////////
// Magic v3
// The basic offensive skill from Trickmaster
case TMW2_MANABOMB:
// This skill takes 100% mana for a 1:1 ratio damage
// And is a trick. Each level improves ratio in 1
// Has no cooldown, so it is powerful with pots
// And is a good starter offensive skill
areaharm(@skillTarget, 0, Sp*@skillLv, HARM_MISC, Ele_Ghost);
Sp=0;
GetManaExp(@skillId, 1);
break;
////////////////////////////////
// XXX: Healing Class
case TMW2_FIRSTAID:
.@PW=90+(10*@skillLv);
// First aid only works on you, so
.@heal=max(AdjustSpellpower(.@PW), AdjustAttackpower(.@PW));
heal .@heal, 0;
GetManaExp(TMW2_HEALING, 1);
break;
case TMW2_HEALING:
.@PW=130+(20*@skillLv);
harm(@skillTarget, -AdjustSpellpower(.@PW), HARM_MISC);
GetManaExp(TMW2_HEALING, 2);
break;
case TMW2_MAGNUSHEAL:
// Area healing
.@PW=200+(20*@skillLv);
.@RG=4+(@skillLv/5);
areaharm(@skillTarget, .@RG, -AdjustSpellpower(.@PW), HARM_MISC, "filter_friendly");
GetManaExp(TMW2_HEALING, 3);
break;
////////////////////////////////
// XXX: Revive Class
case TMW2_RESURRECT:
SK_resurrect(@skillLv, @skillTarget);
break;
////////////////////////////////
// XXX: Fire Class
// (May burn targets for damage over time)
case TMW2_FIREARROW:
.@PW=140+(10*@skillLv);
// 4% chance, 2.5s
sc_start SC_BLOODING, 4500, 1, 400, SCFLAG_NONE, @skillTarget;
harm(@skillTarget, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
GetManaExp(TMW2_FIREBALL, 1);
break;
case TMW2_FIREBALL:
.@PW=140+(10*@skillLv);
.@RG=2+(@skillLv/5);
// 22% chance, 2.5s
sc_start SC_BLOODING, 2500, 1, 4200, SCFLAG_NONE, @skillTarget;
areaharm(@skillTarget, .@RG, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
GetManaExp(TMW2_FIREBALL, 2);
break;
case TMW2_ARMAGEDDON:
.@PW=140+(10*@skillLv);
.@RG=5+(@skillLv/5);
// 18% chance, 3s, 3x3 radius
areasc(.@RG, 6000, SC_BLOODING, BL_MOB|BL_PC|BL_HOM|BL_MER, 1, "filter_hostile", @skillTarget, 1800);
areaharm(@skillTarget, .@RG, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Fire);
GetManaExp(TMW2_FIREBALL, 3);
break;
////////////////////////////////
// XXX: Holy Class
// (Single DPS + AOE)
case TMW2_NAPALMBEAT:
.@PW=35+(5*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
.@RG=2+(@skillLv/3);
areaharm(@skillTarget, .@RG, .@dmg, HARM_MAGI, Ele_Holy);
harm(@skillTarget, .@dmg/10, HARM_MAGI, Ele_Holy);
GetManaExp(TMW2_HOLYLIGHT, 1);
break;
case TMW2_HOLYLIGHT:
.@PW=125+(25*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
areaharm(@skillTarget, 1, .@dmg/5, HARM_MAGI, Ele_Holy);
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Holy);
GetManaExp(TMW2_HOLYLIGHT, 2);
break;
case TMW2_JUDGMENT:
.@PW=250+(50*@skillLv);
.@SPW=60+(15*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
.@dsub=AdjustSpellpower(.@SPW);
.@RG=3+(@skillLv/5);
areaharm(@skillTarget, .@RG, .@dsub, HARM_MAGI, Ele_Holy);
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Holy);
GetManaExp(TMW2_HOLYLIGHT, 3);
break;
////////////////////////////////
// XXX: Wind Class
// (Smaller cooldown than others)
case TMW2_MAGICSTRIKE:
.@PW=125+(25*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Wind);
GetManaExp(TMW2_LIGHTNINGBOLT, 1);
break;
case TMW2_LIGHTNINGBOLT:
.@PW=150+(50*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Wind);
GetManaExp(TMW2_LIGHTNINGBOLT, 2);
break;
case TMW2_TEMPEST:
.@PW=125+(25*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
.@RG=2+(@skillLv/5);
areaharm(@skillTarget, .@RG, .@dmg, HARM_MAGI, Ele_Wind);
GetManaExp(TMW2_LIGHTNINGBOLT, 3);
break;
////////////////////////////////
// XXX: Ice Class
// (May freeze the targets)
case TMW2_FROSTDIVER:
.@PW=80+(10*@skillLv);
// 22% chance, 2.5s
sc_start SC_FREEZE, 2500, 1, 2200, SCFLAG_NONE, @skillTarget;
harm(@skillTarget, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
GetManaExp(TMW2_NILFHEIM, 1);
break;
case TMW2_FROSTNOVA:
.@PW=80+(10*@skillLv);
.@RG=2+(@skillLv/5);
// 18% chance, 3s, 3x3 radius
areasc(.@RG, 3000, SC_FREEZE, BL_MOB|BL_PC|BL_HOM|BL_MER, 1, "filter_hostile", @skillTarget, 1800);
areaharm(@skillTarget, .@RG, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
GetManaExp(TMW2_NILFHEIM, 2);
break;
case TMW2_NILFHEIM:
// Nilfheim cast on self?
.@PW=80+(10*@skillLv);
.@RG=4+(@skillLv/5);
areasc(.@RG, 15000, SC_FREEZE, BL_PC | BL_MOB | BL_MER | BL_HOM, 1, "filter_hostile");
areaharm(getcharid(3), .@RG*3/2, AdjustSpellpower(.@PW), HARM_MAGI, Ele_Water);
// Maybe filter_notme() would work better, indeed
GetManaExp(TMW2_NILFHEIM, 3);
break;
////////////////////////////////
// XXX: Earth Class
// DEF Effects at Gaia Break, more expensive
case TMW2_METEORSTRIKE:
.@PW=130+(20*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
.@TM=1200+(@skillLv*300);
sc_start SC_STUN, .@TM, 1, 800, SCFLAG_NONE, @skillTarget;
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Earth);
GetManaExp(TMW2_METEORSTRIKE, 1);
break;
case TMW2_METEORSHOWER:
.@PW=130+(15*@skillLv);
.@dmg=AdjustSpellpower(.@PW);
.@RG=3+(@skillLv/5);
.@TM=800+(@skillLv*200);
areasc(.@RG, .@TM, SC_STUN, BL_MOB | BL_PC | BL_HOM | BL_MER, 1, "filter_hostile", @skillTarget, 800);
areaharm(@skillTarget, .@RG, .@dmg, HARM_MAGI, Ele_Earth);
GetManaExp(TMW2_METEORSTRIKE, 2);
break;
case TMW2_GAIABREAK:
.@PWA=170+(30*@skillLv);
.@PWB=110+(10*@skillLv);
.@dmg=AdjustSpellpower(.@PWA);
.@dsub=AdjustSpellpower(.@PWB);
areasc(2, 5000, SC_INCDEFRATE, BL_PC, 10, "filter_friendly");
rectharm(@skillTarget, 2, 5, .@dsub, HARM_MAGI, Ele_Earth);
harm(@skillTarget, .@dmg, HARM_MAGI, Ele_Earth);
GetManaExp(TMW2_METEORSTRIKE, 3);
break;
/*
// TODO: Ultimate Skills (T5/0)
// Sanctum: AoE resurrection, HP like Resurrect, 30min+ cooldown and
// inflict ailment on caster.
// [Ultimate] revives the whole map, like @raisemap
case TMW2_SANCTUM:
SK_sanctum();
break;
// Support magic
// STATUS: Awaiting for Alchemy rework
// TODO: Debuffs
// TODO: Buffs
// XXX: Resurrection skills
// [MAPZONE] Need special care because scripts
// [WIP] Maybe a custom recovery and a flag @canRevive ?
// ---------------------------------------------
// Revive: Makes char alive with 5% HP/Lvl
// Resurrect: Makes char alive with ±80% HP and give a protection SC (lvl)
// Redemption: AoE resurrection (HP works like revive, though)
case TMW2_REVIVE:
SK_revive(@skillTarget);
break;
case TMW2_RESSURECT:
SK_ressurect(@skillLv);
break;
// Physical Class - mostly builtin
// TODO: Archery effect-absorb skill
case TMW2_ARROWOFDEVOUR:
//.@x=getstatus(SC_POISON)+getstatus(SC_DPOISON);
// if SC_POISON or SC_DPOISON
break;
case TMW2_ARROWOFURGES:
// if SC_SILENCE or SC_FEAR
break;
case TMW2_ULTIMATEARROW:
// if SC_BLIND or SC_BLOODING
break;
case TMW2_EXECUTION:
// if SC_CURSE or SC_FEAR
break;
case TMW2_SHATTERARROW:
// if SC_STONE or SC_STUN
break;
case TMW2_ARROWOFADVANTAGE:
// IF SC_BURNING or SC_CONFUSION
break;
case TMW2_WAKENINGARROW:
// if SC_SLEEP or SC_DEEP_SLEEP
break;
case TMW2_BURNINGARROW:
// if SC_FREEZE or SC_COLD
break;
// TODO Assassin skill
// Higher chance of success if monster HP+ATK < your own HP
// (Maybe put level on that?) Never works on boss
// Maybe arrow only?
case TMW2_ASSASSINATE:
// SC_COMA TODO
break;
case TMW2_SNARE:
// 100.00% inflict STUN/SLEEP/whatever
// Does not work on boss monsters
// (Maybe the one which makes next hit critical?)
// Was it SC_STONE?
break;
// Arrow pierce? (Maybe arrow shot disregarding defense, HARM_MISC ?)
case TMW2_RICHNESS:
// Passive, bonus2 bAddGetZenyNum, 5, 100; - Better not
break;
// TODO: Effect resisting passives?
// Windwalker... No wait.
// Immunity to knockback? Meeeeh. Easier to have a trait-giving quest.
// After all, we have about 20 traits and you can only pick 5.
// XXX: Sword skills are a 5-skills combo
// No cast time, but cooldown present
// DEF lowers as cast, damage based on combo.
case TMW2_HORIZONTALSLASH:
.@PW=100;
.@PW+=(5*@skillLv);
@SCombo=@skillTarget;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_DIAGONALSLASH:
.@PW=100;
if (LAST_SKILL[0] == TMW2_HORIZONTALSLASH)
.@PW+=20;
if (.@PW >= 120)
.@PW+=30;
if (@SCombo != @skillTarget)
.@PW=100;
.@PW+=(5*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_VERTICALSLASH:
.@PW=100;
if (LAST_SKILL[0] == TMW2_DIAGONALSLASH)
.@PW+=30;
if (LAST_SKILL[1] == TMW2_HORIZONTALSLASH)
.@PW+=20;
if (.@PW >= 150)
.@PW+=100;
if (@SCombo != @skillTarget)
.@PW=100;
.@PW+=(5*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_STAB:
.@PW=100;
if (LAST_SKILL[0] == TMW2_VERTICALSLASH)
.@PW+=40;
if (LAST_SKILL[1] == TMW2_DIAGONALSLASH)
.@PW+=30;
if (LAST_SKILL[2] == TMW2_HORIZONTALSLASH)
.@PW+=20;
if (.@PW >= 190)
.@PW+=200;
if (@SCombo != @skillTarget)
.@PW=100;
.@PW+=(5*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_GRANDBLAST:
.@PW=100;
if (LAST_SKILL[0] == TMW2_STAB)
.@PW+=50;
if (LAST_SKILL[1] == TMW2_VERTICALSLASH)
.@PW+=40;
if (LAST_SKILL[2] == TMW2_DIAGONALSLASH)
.@PW+=30;
if (LAST_SKILL[3] == TMW2_HORIZONTALSLASH)
.@PW+=20;
if (.@PW >= 200)
.@PW+=300;
if (@SCombo != @skillTarget)
.@PW=100;
.@PW+=(5*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Holy);
GetManaExp(@skillId, rand2(2,3));
break;
*/
////////////////////////////////
// XXX: Physical Class (Regular)
case TMW2_FALKONSTRIKE:
.@PW=100+(25*@skillLv);
.@ST=0+(10*@skillLv);
.@TM=100+(90*@skillLv);
sc_start SC_STUN, .@TM, 1, .@ST, SCFLAG_NONE, @skillTarget;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_GROUNDSTRIKE:
.@PW=50+(40*@skillLv);
.@dmg=AdjustAttackpower(.@PW);
.@RG=2+(@skillLv/5);
.@TM=100+(@skillLv*200);
.@ST=500+(100*@skillLv);
.@EF=any(SC_STUN, SC_BLIND, SC_BLOODING, SC_BLIND, SC_BLOODING);
areasc(.@RG, .@TM, .@EF, BL_MOB | BL_PC | BL_HOM | BL_MER, 1, "filter_hostile", @skillTarget, .@ST);
areaharm(@skillTarget, .@RG, .@dmg, HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_SUPREMEATTACK:
.@PW=100+(50*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
////////////////////////////////
// XXX: Physical Class (Archery)
case TMW2_CHARGEDARROW:
.@PW=100+(50*@skillLv);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
case TMW2_ARROWSHOWER:
.@PW=150+(10*@skillLv);
.@dmg=AdjustAttackpower(.@PW);
.@RG=1+(@skillLv/3);
areaharm(@skillTarget, .@RG, .@dmg, HARM_PHYS, Ele_Neutral);
GetManaExp(@skillId, rand2(1,3));
break;
////////////////////////////////
// XXX: Brawling Class
case TMW2_BRAWLING:
// 75x3 = 225
.@PW=70+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
break;
case TMW2_BEARSTRIKE:
// 60x5 = 300
.@PW=55+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
break;
case TMW2_ALLINONE:
// 45x8 = 360
.@PW=40+(5*@skillLv);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
//harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Fire);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Water);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Earth);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Wind);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Holy);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Shadow);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Ghost);
sleep2(10);
// The main elemental-less blast hits all in same square,
// and also hits behind (and on your square)
rectharm(@skillTarget, 0, 1, AdjustAttackpower(.@PW/2), HARM_PHYS, Ele_Neutral); // FIXME: May not fire (properly) if target dies
break;
case TMW2_STUNNINGSTRIKE:
// 70x3 = 210
.@PW=65+(5*@skillLv);
.@TM=1600+(@skillLv*200);
// Using a shield, so power is halved
if (getequipid(EQI_HAND_L) > 0)
.@PW=.@PW/2;
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sleep2(10);
harm(@skillTarget, AdjustAttackpower(.@PW), HARM_PHYS, Ele_Neutral);
sc_start SC_STUN, .@TM, 1, 3333, SCFLAG_NONE, @skillTarget;
break;
////////////////////////////////
// CLASS_OTHER
case TMW2_PARUM:
SK_parum();
break;
case TMW2_DEMURE:
SK_Demure();
break;
case TMW2_DRAGOKIN:
SK_Dragokin();
break;
// Summons which never fail
case TMW2_ZARKOR:
alignment_cansummon();
SummonMagic(@skillId, CaveMaggot, 2, @skillLv);
GetManaExp(@skillId, 1);
break;
// Summons which may fail
case TMW2_KALWULF:
SK_summon(Wolvern, 4, any(3,4));
break;
case TMW2_KALBOO:
SK_summon(Mouboo, 4, any(2,3));
break;
case TMW2_KALSPIKE:
SK_summon(PoisonSpikyMushroom, 4, any(2,3));
break;
case TMW2_CUTEHEART:
SK_summon(Fluffy, 4, any(2,3));
break;
// Slightly more complex summons
case TMW2_LIMERIZER:
SK_summon(any(GreenSlime,AzulSlime,RedSlime,AngryYellowSlime), 2, any(3,4));
break;
case TMW2_FAIRYKINGDOM:
SK_summon(any(FireFairy, EarthFairy, WaterFairy, WindFairy, PoisonFairy), 4, any(3,4));
break;
case TMW2_FAIRYEMPIRE:
SK_summon(any(VanityPixie, HolyPixie, ShadowPixie, NulityPixie), 5, any(4,5));
break;
// More complex summons
case TMW2_KALMURK:
.@mobId=Maggot;
if (abizit() > 4 &&
GHMEMO[GHQ_GetQuestIDByMonsterID(Maggot)] >= 10000 &&
MAGIC_LVL >= 3)
{
.@mobId=any(Maggot, Maggot, Maggot, Maggot, GiantMaggot);
}
SK_summon(.@mobId, (.@mobId == Maggot ? 2 : 4), any(1,2));
break;
case TMW2_HALHISS:
.@mobId=Snake;
if (abizit() > 3 &&
GHMEMO[GHQ_GetQuestIDByMonsterID(MountainSnake)] >= 10000 &&
rand2(1,3) == 2)
{
.@mobId=MountainSnake;
}
SK_summon(.@mobId, 4, any(3,4));
break;
case TMW2_FROZENHEART:
.@mobId=Moggun;
if (rand2(6,12) < (abizit()*2)+1)
{
.@mobId=Yeti;
}
SK_summon(.@mobId, 4, any(3,4));
break;
case TMW2_STONEHEART:
.@mobId=Terranite;
if (rand2(9,12) < (abizit()*2)+1 &&
BaseLevel > 80)
{
.@mobId=TerraniteProtector;
}
SK_summon(.@mobId, 4, any(4,5));
break;
case TMW2_DUCKY:
.@mobId=Duck;
.@q=getq(LilitQuest_PiratesOfSARAH);
if (!alignment_cansummon())
break;
if (abizit() > 4 &&
.@q > 2 &&
MAGIC_LVL >= 3)
{
// GHQ Complete: 33% chances
// Otherwise: 8% chances
if (GHMEMO[GHQ_GetQuestIDByMonsterID(Duck)] >= 10000)
.@mobId=any(Duck, Duck, EliteDuck);
else
.@mobId=any(Duck, Duck, Duck, Duck,
Duck, Duck, Duck, Duck,
Duck, Duck, Duck, EliteDuck);
}
SummonMagic(@skillId, .@mobId, 2);
GetManaExp(@skillId, 1);
break;
// Special exception
case TMW2_TRANSMIGRATION:
doevent("sk#mkpot::OnCall");
break;
// Experience only
case KN_AUTOCOUNTER:
case SN_SHARPSHOOTING:
case HW_MAGICPOWER:
case SM_PROVOKE:
case SN_WINDWALK:
case SO_FIREWALK:
case TF_BACKSLIDING:
case MG_FIREWALL:
case ALL_FULL_THROTTLE:
case GC_DARKILLUSION:
case NV_TRICKDEAD:
GetManaExp(@skillId, rand2(1,3));
break;
}
// Debug
if ($@GM_OVERRIDE)
debugmes "Cast skill %d on level %d - Target %d",
@skillId, @skillLv, @skillTarget;
// Cleanup (double-safe)
@skillTarget = 0;
return;
}
// When you kill a player, some special care is needed
// Only a few maps will give you experience for PK: Tulimshar's Guards Arena,
// Frostia Imperial PVP Arena, Call Of Dusty, Arena Quirino Voraz.
// HUB_PvP ( ) - killedrid must be set
function script HUB_PvP {
// Update global PK count
$PLAYERS_KILLED+=1;
// Prepare local variables
.@atk=get_BR(getcharid(3));
.@def=get_BR(killedrid);
.@honor=calc_HR(getcharid(3), killedrid);
.@bxp=max(readparam(BaseLevel, killedrid), .@def);
.@jxp=readparam(JobLevel, killedrid);
.@m$=getmap();
.@gm=getgroupid(killedrid);
// This is an official PVP Map
if (ispvpmap(.@m$)) {
// Honorable Death
if (.@honor >= 0) {
HONOR+=.@honor;
} else {
// Dishonorable... But... Legit?
if (is_bandit(killedrid) || getmap() == "001-8")
.@honor=1;
HONOR+=.@honor; // It's negative.
}
// PvP, and a GM was slain
if (.@gm >= 80) {
rentitem MurdererCrown, (86400*7); // 1 week
kamibroadcast(strcharinfo(0)+" killed Game Master \""+strcharinfo(0, "", killedrid)+"\", and now may wear a "+getitemlink(MurdererCrown)+" for a week.");
}
// It was a duel!
} else {
// Honorable Duel: HONOR +30%
if (.@honor > 0)
HONOR+=max(1, .@honor*3/10);
else if (.@honor < 0)
HONOR+=1;
// ^ Dishonorable duel, but was a duel!
}
// Report about honor
dispbottom l("%d vs %d: Honor (%d)", .@atk, .@def, .@honor);
// TODO: Start using readparam2() to read if the opponent was worthy
// That is, read total attack, defense, HP, evasion and hit chance
// And compare with your own readparam2(), then use a % and a table
// based on your (assassin's) level.
if (compare(.@m$, "001-8")) {
// Quirino Voraz PVP Arena
// You get 5 times killed player level, and 1 time job level
getexp .@bxp*5, .@jxp;
} else if (compare(.@m$, "ARENA") || compare(.@m$, "003-13")) {
// Tulimshar Duel Arena
// You get 3 times killed player level, and 2 times job level
getexp .@bxp*3, .@jxp*2;
} else if (compare(.@m$, "001-10")) {
// Call Of Dusty
// You get 3 times killed player level, and 3 times job level
getexp .@bxp*3, .@jxp*3;
} else if (compare(.@m$, "001-10-1")) {
// Call Of Dusty Boss Room
// You _may_ get a Bottled Dusty at random, but dead player status affect
if (.@def > .@atk/10) {
if (rand2(0,250) < readparam2(bLuk)+readparam2(bLuk, killedrid))
getitem BottledDust, any(1,1,2);
}
} else {
// Anywhere else
// You get 0.5 times killed player level, and 0 times job level
getexp (.@bxp/2), 0;
}
return;
}
// HUB_PCBonus ()
function script HUB_PCBonus {
/* Rebirth Traits */
if (PCBONUS & PCB_ATKBONUS) {
bonus bAtk, 25;
}
if (PCBONUS & PCB_MATKBONUS) {
bonus bMatk, 25;
}
if (PCBONUS & PCB_DEFBONUS) {
bonus bDef, 20;
}
if (PCBONUS & PCB_MDEFBONUS) {
bonus bMdef, 20;
}
if (PCBONUS & PCB_EVDBONUS) {
bonus bFlee, 20;
}
if (PCBONUS & PCB_HITBONUS) {
bonus bHit, 25;
}
if (PCBONUS & PCB_CRITBONUS) {
bonus bCritical, 5;
}
if (PCBONUS & PCB_DOUBLEATK) {
bonus bDoubleAddRate, 5;
}
if (PCBONUS & PCB_ALLSTATS) {
bonus bAllStats, 1;
}
if (PCBONUS & PCB_HPBONUS) {
bonus bMaxHP, 500;
}
if (PCBONUS & PCB_MPBONUS) {
bonus bMaxSP, 200;
}
if (PCBONUS & PCB_ASPDBONUS) {
bonus bAspd, 10;
}
if (PCBONUS & PCB_WSPDBONUS) {
bonus bSpeedAddRate, 5;
}
if (PCBONUS & PCB_WEIGHTBONUS) {
bonus bAddMaxWeight, 1000;
}
if (PCBONUS & PCB_EXPBONUS) {
bonus2 bExpAddRace, RC_All, 10;
}
if (PCBONUS & PCB_NOKNOCKBACK) {
bonus bNoKnockback, 1;
}
if (PCBONUS & PCB_SPLASHMASTER) {
bonus bSplashRange, 1;
}
if (PCBONUS & PCB_RANGEMASTER) {
bonus bAtkRange, 1;
}
if (Class != Savior && !(PCBONUS & PCB_LEGENDARY)) {
bonus2 bCriticalAddRace, RC_Legendary, -25;
bonus2 bSPDrainValueRace, RC_Legendary, -5;
bonus2 bAddRace, RC_Legendary, -40;
bonus2 bMagicAddRace, RC_Legendary, -20;
}
if (PCBONUS & PCB_LEGENDARY) {
bonus bDefRatioAtkRace, RC_Legendary;
}
/* Passive Skills */
bonus2 bSubRace, RC_Legendary, getskilllv(AL_DP)-10;
if (getq(LoFQuest_Barbara) >= 4 || getq(General_Narrator) >= 19)
bonus bMaxSP, 40;
return;
}