Server upload

RD Core / Commit #2

Commit Server upload

Alex Alex committed 18/10/2026 23:38 main Patch
1,106 files +223,404 -0
A data/lib/core/actionids.lua +7-0 View file
@@ -0,0 +1,7 @@
1+actionIds = {
2+ sandHole = 100, -- hidden sand hole
3+ pickHole = 105, -- hidden mud hole
4+ levelDoor = 1000, -- level door
5+ citizenship = 30020, -- citizenship teleport
6+ citizenshipLast = 30050, -- citizenship teleport last
7+}
A data/lib/core/combat.lua +21-0 View file
@@ -0,0 +1,21 @@
1+function Combat:getPositions(creature, variant)
2+ local positions = {}
3+ function onTargetTile(creature, position)
4+ positions[#positions + 1] = position
5+ end
6+
7+ self:setCallback(CALLBACK_PARAM_TARGETTILE, "onTargetTile")
8+ self:execute(creature, variant)
9+ return positions
10+end
11+
12+function Combat:getTargets(creature, variant)
13+ local targets = {}
14+ function onTargetCreature(creature, target)
15+ targets[#targets + 1] = target
16+ end
17+
18+ self:setCallback(CALLBACK_PARAM_TARGETCREATURE, "onTargetCreature")
19+ self:execute(creature, variant)
20+ return targets
21+end
A data/lib/core/condition_subids.lua +130-0 View file
@@ -0,0 +1,130 @@
1+CONDITION_SUBIDS = {
2+ -- PASSIVES & GENERAL
3+ DEADLY_DUELIST = 1,
4+ DIVINE_PURPOSE = 2,
5+ EXPLOITATION = 3,
6+ FLEETFOOTED = 4,
7+ HALO = 5,
8+ HATRED = 6,
9+ MARKED_FOR_DEATH = 7,
10+ REINFORCED_SHIELD = 8,
11+ LIVING_WOOD = 9,
12+ FIREBALL_DOTSTACK = 10,
13+ ARCANE_POTENCY = 11,
14+ BARRIER_OF_PROTECTION = 12,
15+ BLESSED_EARTH = 13,
16+ BLOODSEEKER = 14,
17+ BRUTAL_STRIKE = 15,
18+ CONCUSSIVE_SHOT = 16,
19+ CURSE = 17,
20+ DARK_BARRIER = 18,
21+ DISENGAGE = 19,
22+ ECHO = 20,
23+ EXPLOSIVE_ARROWS = 21,
24+ FROST_ARMOR = 22,
25+ FROSTBOLT_DOTSTACK = 23,
26+ HASTE = 24,
27+ HEX = 25,
28+ ILLUSIVE = 26,
29+ LAVA_GLOB = 27,
30+ LEECH = 28,
31+ LEECH_REGEN = 29,
32+ LIFE_DRAIN = 30,
33+ LIVING_SAINT = 31,
34+ MAGIC_RUPTURE = 32,
35+ MAGIC_RUPTURE_HASTE = 33,
36+ MAGIC_SHIELD = 34,
37+ MEND = 35,
38+ PARTY_RECOVERY = 36,
39+ RAMPAGE = 37,
40+ REFRESH = 38,
41+ REGENERATE = 39,
42+ SHADOWSTEP = 40,
43+ SHIELDBREAK = 41,
44+ SPEED = 42,
45+ SPIKED_CHAINS = 43,
46+ SPIRIT_SHIELD = 44,
47+ STEAL_GOLD_STRONG_LOOT = 45,
48+ UNCHAINED = 46,
49+ VIPER_ARROW = 47,
50+ WHIP_TONGUE = 48,
51+ WIND_ARROW = 49,
52+ ZEALOTS_FLAME = 50,
53+
54+ OUTFIT = {
55+ WAGON = 100,
56+ },
57+
58+ MONSTER = {
59+ BLOODBOIL = 201,
60+ BOMB_THROW = 202,
61+ DARK_BOLT = 203,
62+ DOUBLE_FIRE_PUNCH = 204,
63+ DOUBLE_SCRATCH = 205,
64+ ELECTRIC_PULSE = 206,
65+ FIRE_PUNCH = 207,
66+ GOBLIN_WARCRY = 208,
67+ HOG_WARCRY = 209,
68+ LEAF_THROW = 210,
69+ MANA_POISON = 211,
70+ NATURE_BOLT = 212,
71+ OVERCHARGE = 213,
72+ POISON = 214,
73+ POISONOUS_NEEDLE = 215,
74+ RAGE = 216,
75+ SCRATCH = 217,
76+ SPEAR_THROW_HEMORAGE = 218,
77+ SPINNING_AXE = 219,
78+ TROLL_WARCRY = 220,
79+ VAMPIRIC_BITE = 221,
80+ WAR_HORN = 222,
81+ },
82+
83+ CARDS = {
84+ ALL_SEEING_EYES = 1001,
85+ APEX_PREDATOR = 1002,
86+ ASPIRATION = 1003,
87+ BEE_STING = 1004,
88+ CHALLENGER = 1005,
89+ CRIPPLING_BITE = 1006,
90+ CRIPPLING_GLANDS = 1007,
91+ DEADLY_DUELIST = 1008,
92+ DEATH_FROM_ABOVE = 1009,
93+ EXPLOITATION = 1010,
94+ FIGHTERS_FRENZY = 1011,
95+ GATHERER = 1012,
96+ GIGANTISM = 1013,
97+ GRASPING_ROOTS = 1014,
98+ GRIZZLY_STRENGTH = 1015,
99+ HALF_SPLIT_WOOD = 1016,
100+ HARDENED = 1017,
101+ HASTED = 1018,
102+ HOG_WAR_RITES = 1019,
103+ HOLY_BARRIER = 1020,
104+ KEEN_NATURE = 1021,
105+ LIGHTFOOTED = 1022,
106+ LIVING_WOOD = 1023,
107+ PLUNDERER = 1024,
108+ PRESSING_CHARGE = 1025,
109+ PROSPECTOR = 1026,
110+ PYRO_OVERLOAD = 1027,
111+ PYRO_OVERLOAD_DEBUFF = 1028,
112+ QUICK_RELOAD = 1029,
113+ RAZERWIRE = 1030,
114+ REPTILE = 1031,
115+ RUPTURE = 1032,
116+ RUPTURING_BITE = 1033,
117+ RUPTURING_GLANDS = 1034,
118+ SHARPENED_TIP = 1035,
119+ SPIKED_WEAPON = 1036,
120+ STEAM_POWERED = 1037,
121+ STURDY_FOOTED = 1038,
122+ SUPERSONIC = 1039,
123+ SWAMP_LURKER = 1040,
124+ TINKERER = 1041,
125+ TORCHED = 1042,
126+ TROLL_GENETICS = 1043,
127+ VENOMOUS_GLANDS = 1044,
128+ WISDOM = 1045,
129+ }
130+}
A data/lib/core/constants.lua +6-0 View file
@@ -0,0 +1,6 @@
1+CONTAINER_POSITION = 0xFFFF
2+
3+DAMAGELIST_EXPONENTIAL_DAMAGE = 0
4+DAMAGELIST_LOGARITHMIC_DAMAGE = 1
5+DAMAGELIST_VARYING_PERIOD = 2
6+DAMAGELIST_CONSTANT_PERIOD = 3
A data/lib/core/container.lua +48-0 View file
@@ -0,0 +1,48 @@
1+function Container.isContainer(self)
2+ return true
3+end
4+
5+function Container.createLootItem(self, item)
6+ if self:getEmptySlots() == 0 then
7+ return true
8+ end
9+
10+ local itemCount = 0
11+ local randvalue = getLootRandom()
12+ if randvalue < item.chance then
13+ if ItemType(item.itemId):isStackable() then
14+ itemCount = randvalue % item.maxCount + 1
15+ else
16+ itemCount = 1
17+ end
18+ end
19+
20+ if itemCount > 0 then
21+ local tmpItem = self:addItem(item.itemId, math.min(itemCount, 100))
22+ if not tmpItem then
23+ return false
24+ end
25+
26+ if tmpItem:isContainer() then
27+ for i = 1, #item.childLoot do
28+ if not tmpItem:createLootItem(item.childLoot[i]) then
29+ tmpItem:remove()
30+ return false
31+ end
32+ end
33+ end
34+
35+ if item.subType ~= -1 then
36+ tmpItem:setAttribute(ITEM_ATTRIBUTE_CHARGES, item.subType)
37+ end
38+
39+ if item.actionId ~= -1 then
40+ tmpItem:setActionId(item.actionId)
41+ end
42+
43+ if item.text and item.text ~= "" then
44+ tmpItem:setText(item.text)
45+ end
46+ end
47+ return true
48+end
A data/lib/core/core.lua +19-0 View file
@@ -0,0 +1,19 @@
1+-- Note: The library of storages must be loaded previously to the other libraries.
2+dofile('data/lib/core/storages.lua')
3+
4+dofile('data/lib/core/achievements.lua')
5+dofile('data/lib/core/actionids.lua')
6+dofile('data/lib/core/combat.lua')
7+dofile('data/lib/core/constants.lua')
8+dofile('data/lib/core/condition_subids.lua')
9+dofile('data/lib/core/container.lua')
10+dofile('data/lib/core/creature.lua')
11+dofile('data/lib/core/game.lua')
12+dofile('data/lib/core/item.lua')
13+dofile('data/lib/core/itemtype.lua')
14+dofile('data/lib/core/party.lua')
15+dofile('data/lib/core/player.lua')
16+dofile('data/lib/core/position.lua')
17+dofile('data/lib/core/teleport.lua')
18+dofile('data/lib/core/tile.lua')
19+dofile('data/lib/core/vocation.lua')
A data/lib/core/creature.lua +176-0 View file
@@ -0,0 +1,176 @@
1+function Creature.getClosestFreePosition(self, position, maxRadius, mustBeReachable)
2+ maxRadius = maxRadius or 1
3+
4+ -- backward compatability (extended)
5+ if maxRadius == true then
6+ maxRadius = 2
7+ end
8+
9+ local checkPosition = Position(position)
10+ for radius = 0, maxRadius do
11+ checkPosition.x = checkPosition.x - math.min(1, radius)
12+ checkPosition.y = checkPosition.y + math.min(1, radius)
13+
14+ local total = math.max(1, radius * 8)
15+ for i = 1, total do
16+ if radius > 0 then
17+ local direction = math.floor((i - 1) / (radius * 2))
18+ checkPosition:getNextPosition(direction)
19+ end
20+
21+ local tile = Tile(checkPosition)
22+ if tile:getCreatureCount() == 0 and not tile:hasProperty(CONST_PROP_IMMOVABLEBLOCKSOLID) and
23+ (not mustBeReachable or self:getPathTo(checkPosition)) then
24+ return checkPosition
25+ end
26+ end
27+ end
28+ return Position()
29+end
30+
31+function Creature.getPlayer(self)
32+ return self:isPlayer() and self or nil
33+end
34+
35+function Creature.isContainer(self)
36+ return false
37+end
38+
39+function Creature.isItem(self)
40+ return false
41+end
42+
43+function Creature.isMonster(self)
44+ return false
45+end
46+
47+function Creature.isNpc(self)
48+ return false
49+end
50+
51+function Creature.isPlayer(self)
52+ return false
53+end
54+
55+function Creature.isTeleport(self)
56+ return false
57+end
58+
59+function Creature.isTile(self)
60+ return false
61+end
62+
63+function Creature:setMonsterOutfit(monster, time)
64+ local monsterType = MonsterType(monster)
65+ if not monsterType then
66+ return false
67+ end
68+
69+ if self:isPlayer() and not (self:hasFlag(PlayerFlag_CanIllusionAll) or monsterType:isIllusionable()) then
70+ return false
71+ end
72+
73+ local condition = Condition(CONDITION_OUTFIT)
74+ condition:setOutfit(monsterType:getOutfit())
75+ condition:setTicks(time)
76+ self:addCondition(condition)
77+
78+ return true
79+end
80+
81+function Creature:setItemOutfit(item, time)
82+ local itemType = ItemType(item)
83+ if not itemType then
84+ return false
85+ end
86+
87+ local condition = Condition(CONDITION_OUTFIT)
88+ condition:setOutfit({
89+ lookTypeEx = itemType:getId()
90+ })
91+ condition:setTicks(time)
92+ self:addCondition(condition)
93+
94+ return true
95+end
96+
97+function Creature:addSummon(monster)
98+ local summon = Monster(monster)
99+ if not summon then
100+ return false
101+ end
102+
103+ summon:setTarget(nil)
104+ summon:setFollowCreature(nil)
105+ summon:setDropLoot(false)
106+ summon:setSkillLoss(false)
107+ summon:setMaster(self)
108+ summon:getPosition():notifySummonAppear(summon)
109+
110+ return true
111+end
112+
113+function Creature:removeSummon(monster)
114+ local summon = Monster(monster)
115+ if not summon or summon:getMaster() ~= self then
116+ return false
117+ end
118+
119+ summon:setTarget(nil)
120+ summon:setFollowCreature(nil)
121+ summon:setDropLoot(true)
122+ summon:setSkillLoss(true)
123+ summon:setMaster(nil)
124+
125+ return true
126+end
127+
128+function Creature:addDamageCondition(target, type, list, damage, period, rounds)
129+ if damage <= 0 or not target or target:isImmune(type) then
130+ return false
131+ end
132+
133+ local condition = Condition(type)
134+ condition:setParameter(CONDITION_PARAM_OWNER, self:getId())
135+ condition:setParameter(CONDITION_PARAM_DELAYED, true)
136+
137+ if list == DAMAGELIST_EXPONENTIAL_DAMAGE then
138+ local exponent, value = -10, 0
139+ while value < damage do
140+ value = math.floor(10 * math.pow(1.2, exponent) + 0.5)
141+ condition:addDamage(1, period or 4000, -value)
142+
143+ if value >= damage then
144+ local permille = math.random(10, 1200) / 1000
145+ condition:addDamage(1, period or 4000, -math.max(1, math.floor(value * permille + 0.5)))
146+ else
147+ exponent = exponent + 1
148+ end
149+ end
150+ elseif list == DAMAGELIST_LOGARITHMIC_DAMAGE then
151+ local n, value = 0, damage
152+ while value > 0 do
153+ value = math.floor(damage * math.pow(2.718281828459, -0.05 * n) + 0.5)
154+ if value ~= 0 then
155+ condition:addDamage(1, period or 4000, -value)
156+ n = n + 1
157+ end
158+ end
159+ elseif list == DAMAGELIST_VARYING_PERIOD then
160+ for _ = 1, rounds do
161+ condition:addDamage(1, math.random(period[1], period[2]) * 1000, -damage)
162+ end
163+ elseif list == DAMAGELIST_CONSTANT_PERIOD then
164+ condition:addDamage(rounds, period * 1000, -damage)
165+ end
166+
167+ target:addCondition(condition)
168+ return true
169+end
170+
171+function Creature:canAccessPz()
172+ if self:isMonster() or (self:isPlayer() and self:isPzLocked()) then
173+ return false
174+ end
175+ return true
176+end
A data/lib/core/game.lua +68-0 View file
@@ -0,0 +1,68 @@
1+function Game.broadcastMessage(message, messageType)
2+ if not messageType then
3+ messageType = MESSAGE_STATUS_WARNING
4+ end
5+
6+ for _, player in ipairs(Game.getPlayers()) do
7+ player:sendTextMessage(messageType, message)
8+ end
9+end
10+
11+function Game.convertIpToString(ip)
12+ local band = bit.band
13+ local rshift = bit.rshift
14+ return string.format("%d.%d.%d.%d",
15+ band(ip, 0xFF),
16+ band(rshift(ip, 8), 0xFF),
17+ band(rshift(ip, 16), 0xFF),
18+ rshift(ip, 24)
19+ )
20+end
21+
22+function Game.getReverseDirection(direction)
23+ if direction == WEST then
24+ return EAST
25+ elseif direction == EAST then
26+ return WEST
27+ elseif direction == NORTH then
28+ return SOUTH
29+ elseif direction == SOUTH then
30+ return NORTH
31+ elseif direction == NORTHWEST then
32+ return SOUTHEAST
33+ elseif direction == NORTHEAST then
34+ return SOUTHWEST
35+ elseif direction == SOUTHWEST then
36+ return NORTHEAST
37+ elseif direction == SOUTHEAST then
38+ return NORTHWEST
39+ end
40+ return NORTH
41+end
42+
43+function Game.getSkillType(weaponType)
44+ if weaponType == WEAPON_CLUB then
45+ return SKILL_CLUB
46+ elseif weaponType == WEAPON_SWORD then
47+ return SKILL_SWORD
48+ elseif weaponType == WEAPON_AXE then
49+ return SKILL_AXE
50+ elseif weaponType == WEAPON_DISTANCE then
51+ return SKILL_DISTANCE
52+ elseif weaponType == WEAPON_SHIELD then
53+ return SKILL_SHIELD
54+ end
55+ return SKILL_FIST
56+end
57+
58+if not globalStorageTable then
59+ globalStorageTable = {}
60+end
61+
62+function Game.getStorageValue(key)
63+ return globalStorageTable[key]
64+end
65+
66+function Game.setStorageValue(key, value)
67+ globalStorageTable[key] = value
68+end
A data/lib/core/item.lua +31-0 View file
@@ -0,0 +1,31 @@
1+function Item.getType(self)
2+ return ItemType(self:getId())
3+end
4+
5+function Item.isContainer(self)
6+ return false
7+end
8+
9+function Item.isCreature(self)
10+ return false
11+end
12+
13+function Item.isMonster(self)
14+ return false
15+end
16+
17+function Item.isNpc(self)
18+ return false
19+end
20+
21+function Item.isPlayer(self)
22+ return false
23+end
24+
25+function Item.isTeleport(self)
26+ return false
27+end
28+
29+function Item.isTile(self)
30+ return false
31+end
A data/lib/core/itemtype.lua +16-0 View file
@@ -0,0 +1,16 @@
1+local slotBits = {
2+ [CONST_SLOT_HEAD] = SLOTP_HEAD,
3+ [CONST_SLOT_NECKLACE] = SLOTP_NECKLACE,
4+ [CONST_SLOT_BACKPACK] = SLOTP_BACKPACK,
5+ [CONST_SLOT_ARMOR] = SLOTP_ARMOR,
6+ [CONST_SLOT_RIGHT] = SLOTP_RIGHT,
7+ [CONST_SLOT_LEFT] = SLOTP_LEFT,
8+ [CONST_SLOT_LEGS] = SLOTP_LEGS,
9+ [CONST_SLOT_FEET] = SLOTP_FEET,
10+ [CONST_SLOT_RING] = SLOTP_RING,
11+ [CONST_SLOT_AMMO] = SLOTP_AMMO
12+}
13+
14+function ItemType.usesSlot(self, slot)
15+ return bit.band(self:getSlotPosition(), slotBits[slot] or 0) ~= 0
16+end
A data/lib/core/json.lua +397-0 View file
@@ -0,0 +1,397 @@
1+--
2+-- json.lua
3+--
4+-- Copyright (c) 2018 rxi
5+--
6+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
7+-- this software and associated documentation files (the "Software"), to deal in
8+-- the Software without restriction, including without limitation the rights to
9+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10+-- of the Software, and to permit persons to whom the Software is furnished to do
11+-- so, subject to the following conditions:
12+--
13+-- The above copyright notice and this permission notice shall be included in all
14+-- copies or substantial portions of the Software.
15+--
16+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+-- SOFTWARE.
23+--
24+
25+json = { _version = "0.1.1" }
26+
27+-------------------------------------------------------------------------------
28+-- Encode
29+-------------------------------------------------------------------------------
30+
31+local encode
32+
33+local escape_char_map = {
34+ [ "\\" ] = "\\\\",
35+ [ "\"" ] = "\\\"",
36+ [ "\b" ] = "\\b",
37+ [ "\f" ] = "\\f",
38+ [ "\n" ] = "\\n",
39+ [ "\r" ] = "\\r",
40+ [ "\t" ] = "\\t",
41+}
42+
43+local escape_char_map_inv = { [ "\\/" ] = "/" }
44+for k, v in pairs(escape_char_map) do
45+ escape_char_map_inv[v] = k
46+end
47+
48+
49+local function escape_char(c)
50+ return escape_char_map[c] or string.format("\\u%04x", c:byte())
51+end
52+
53+
54+local function encode_nil(val)
55+ return "null"
56+end
57+
58+
59+local function encode_table(val, stack)
60+ local res = {}
61+ stack = stack or {}
62+
63+ -- Circular reference?
64+ if stack[val] then error("circular reference") end
65+
66+ stack[val] = true
67+
68+ if val[1] ~= nil or next(val) == nil then
69+ -- Treat as array -- check keys are valid and it is not sparse
70+ local n = 0
71+ for k in pairs(val) do
72+ if type(k) ~= "number" then
73+ error("invalid table: mixed or invalid key types")
74+ end
75+ n = n + 1
76+ end
77+ if n ~= #val then
78+ error("invalid table: sparse array")
79+ end
80+ -- Encode
81+ for i, v in ipairs(val) do
82+ table.insert(res, encode(v, stack))
83+ end
84+ stack[val] = nil
85+ return "[" .. table.concat(res, ",") .. "]"
86+
87+ else
88+ -- Treat as an object
89+ for k, v in pairs(val) do
90+ if type(k) ~= "string" then
91+ error("invalid table: mixed or invalid key types")
92+ end
93+ table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
94+ end
95+ stack[val] = nil
96+ return "{" .. table.concat(res, ",") .. "}"
97+ end
98+end
99+
100+
101+local function encode_string(val)
102+ return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
103+end
104+
105+
106+local function encode_number(val)
107+ -- Check for NaN, -inf and inf
108+ if val ~= val or val <= -math.huge or val >= math.huge then
109+ error("unexpected number value '" .. tostring(val) .. "'")
110+ end
111+ return string.format("%.14g", val)
112+end
113+
114+
115+local type_func_map = {
116+ [ "nil" ] = encode_nil,
117+ [ "table" ] = encode_table,
118+ [ "string" ] = encode_string,
119+ [ "number" ] = encode_number,
120+ [ "boolean" ] = tostring,
121+}
122+
123+
124+encode = function(val, stack)
125+ local t = type(val)
126+ local f = type_func_map[t]
127+ if f then
128+ return f(val, stack)
129+ end
130+ error("unexpected type '" .. t .. "'")
131+end
132+
133+
134+function json.encode(val)
135+ return ( encode(val) )
136+end
137+
138+
139+-------------------------------------------------------------------------------
140+-- Decode
141+-------------------------------------------------------------------------------
142+
143+local parse
144+
145+local function create_set(...)
146+ local res = {}
147+ for i = 1, select("#", ...) do
148+ res[ select(i, ...) ] = true
149+ end
150+ return res
151+end
152+
153+local space_chars = create_set(" ", "\t", "\r", "\n")
154+local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
155+local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
156+local literals = create_set("true", "false", "null")
157+
158+local literal_map = {
159+ [ "true" ] = true,
160+ [ "false" ] = false,
161+ [ "null" ] = nil,
162+}
163+
164+
165+local function next_char(str, idx, set, negate)
166+ for i = idx, #str do
167+ if set[str:sub(i, i)] ~= negate then
168+ return i
169+ end
170+ end
171+ return #str + 1
172+end
173+
174+
175+local function decode_error(str, idx, msg)
176+ local line_count = 1
177+ local col_count = 1
178+ for i = 1, idx - 1 do
179+ col_count = col_count + 1
180+ if str:sub(i, i) == "\n" then
181+ line_count = line_count + 1
182+ col_count = 1
183+ end
184+ end
185+ error( string.format("%s at line %d col %d", msg, line_count, col_count) )
186+end
187+
188+
189+local function codepoint_to_utf8(n)
190+ -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
191+ local f = math.floor
192+ if n <= 0x7f then
193+ return string.char(n)
194+ elseif n <= 0x7ff then
195+ return string.char(f(n / 64) + 192, n % 64 + 128)
196+ elseif n <= 0xffff then
197+ return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
198+ elseif n <= 0x10ffff then
199+ return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
200+ f(n % 4096 / 64) + 128, n % 64 + 128)
201+ end
202+ error( string.format("invalid unicode codepoint '%x'", n) )
203+end
204+
205+
206+local function parse_unicode_escape(s)
207+ local n1 = tonumber( s:sub(3, 6), 16 )
208+ local n2 = tonumber( s:sub(9, 12), 16 )
209+ -- Surrogate pair?
210+ if n2 then
211+ return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
212+ else
213+ return codepoint_to_utf8(n1)
214+ end
215+end
216+
217+
218+local function parse_string(str, i)
219+ local has_unicode_escape = false
220+ local has_surrogate_escape = false
221+ local has_escape = false
222+ local last
223+ for j = i + 1, #str do
224+ local x = str:byte(j)
225+
226+ if x < 32 then
227+ decode_error(str, j, "control character in string")
228+ end
229+
230+ if last == 92 then -- "\\" (escape char)
231+ if x == 117 then -- "u" (unicode escape sequence)
232+ local hex = str:sub(j + 1, j + 5)
233+ if not hex:find("%x%x%x%x") then
234+ decode_error(str, j, "invalid unicode escape in string")
235+ end
236+ if hex:find("^[dD][89aAbB]") then
237+ has_surrogate_escape = true
238+ else
239+ has_unicode_escape = true
240+ end
241+ else
242+ local c = string.char(x)
243+ if not escape_chars[c] then
244+ decode_error(str, j, "invalid escape char '" .. c .. "' in string")
245+ end
246+ has_escape = true
247+ end
248+ last = nil
249+
250+ elseif x == 34 then -- '"' (end of string)
251+ local s = str:sub(i + 1, j - 1)
252+ if has_surrogate_escape then
253+ s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
254+ end
255+ if has_unicode_escape then
256+ s = s:gsub("\\u....", parse_unicode_escape)
257+ end
258+ if has_escape then
259+ s = s:gsub("\\.", escape_char_map_inv)
260+ end
261+ return s, j + 1
262+
263+ else
264+ last = x
265+ end
266+ end
267+ decode_error(str, i, "expected closing quote for string")
268+end
269+
270+
271+local function parse_number(str, i)
272+ local x = next_char(str, i, delim_chars)
273+ local s = str:sub(i, x - 1)
274+ local n = tonumber(s)
275+ if not n then
276+ decode_error(str, i, "invalid number '" .. s .. "'")
277+ end
278+ return n, x
279+end
280+
281+
282+local function parse_literal(str, i)
283+ local x = next_char(str, i, delim_chars)
284+ local word = str:sub(i, x - 1)
285+ if not literals[word] then
286+ decode_error(str, i, "invalid literal '" .. word .. "'")
287+ end
288+ return literal_map[word], x
289+end
290+
291+
292+local function parse_array(str, i)
293+ local res = {}
294+ local n = 1
295+ i = i + 1
296+ while 1 do
297+ local x
298+ i = next_char(str, i, space_chars, true)
299+ -- Empty / end of array?
300+ if str:sub(i, i) == "]" then
301+ i = i + 1
302+ break
303+ end
304+ -- Read token
305+ x, i = parse(str, i)
306+ res[n] = x
307+ n = n + 1
308+ -- Next token
309+ i = next_char(str, i, space_chars, true)
310+ local chr = str:sub(i, i)
311+ i = i + 1
312+ if chr == "]" then break end
313+ if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
314+ end
315+ return res, i
316+end
317+
318+
319+local function parse_object(str, i)
320+ local res = {}
321+ i = i + 1
322+ while 1 do
323+ local key, val
324+ i = next_char(str, i, space_chars, true)
325+ -- Empty / end of object?
326+ if str:sub(i, i) == "}" then
327+ i = i + 1
328+ break
329+ end
330+ -- Read key
331+ if str:sub(i, i) ~= '"' then
332+ decode_error(str, i, "expected string for key")
333+ end
334+ key, i = parse(str, i)
335+ -- Read ':' delimiter
336+ i = next_char(str, i, space_chars, true)
337+ if str:sub(i, i) ~= ":" then
338+ decode_error(str, i, "expected ':' after key")
339+ end
340+ i = next_char(str, i + 1, space_chars, true)
341+ -- Read value
342+ val, i = parse(str, i)
343+ -- Set
344+ res[key] = val
345+ -- Next token
346+ i = next_char(str, i, space_chars, true)
347+ local chr = str:sub(i, i)
348+ i = i + 1
349+ if chr == "}" then break end
350+ if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
351+ end
352+ return res, i
353+end
354+
355+
356+local char_func_map = {
357+ [ '"' ] = parse_string,
358+ [ "0" ] = parse_number,
359+ [ "1" ] = parse_number,
360+ [ "2" ] = parse_number,
361+ [ "3" ] = parse_number,
362+ [ "4" ] = parse_number,
363+ [ "5" ] = parse_number,
364+ [ "6" ] = parse_number,
365+ [ "7" ] = parse_number,
366+ [ "8" ] = parse_number,
367+ [ "9" ] = parse_number,
368+ [ "-" ] = parse_number,
369+ [ "t" ] = parse_literal,
370+ [ "f" ] = parse_literal,
371+ [ "n" ] = parse_literal,
372+ [ "[" ] = parse_array,
373+ [ "{" ] = parse_object,
374+}
375+
376+
377+parse = function(str, idx)
378+ local chr = str:sub(idx, idx)
379+ local f = char_func_map[chr]
380+ if f then
381+ return f(str, idx)
382+ end
383+ decode_error(str, idx, "unexpected character '" .. chr .. "'")
384+end
385+
386+
387+function json.decode(str)
388+ if type(str) ~= "string" then
389+ error("expected argument of type string, got " .. type(str))
390+ end
391+ local res, idx = parse(str, next_char(str, 1, space_chars, true))
392+ idx = next_char(str, idx, space_chars, true)
393+ if idx <= #str then
394+ decode_error(str, idx, "trailing garbage")
395+ end
396+ return res
397+end
A data/lib/core/party.lua +10-0 View file
@@ -0,0 +1,10 @@
1+function Party.broadcastPartyLoot(self, text)
2+ self:getLeader():sendTextMessage(MESSAGE_INFO_DESCR, text)
3+ local membersList = self:getMembers()
4+ for i = 1, #membersList do
5+ local player = membersList[i]
6+ if player then
7+ player:sendTextMessage(MESSAGE_INFO_DESCR, text)
8+ end
9+ end
10+end
A data/lib/core/player.lua +217-0 View file
@@ -0,0 +1,217 @@
1+local foodCondition = Condition(CONDITION_REGENERATION, CONDITIONID_DEFAULT)
2+
3+function Player.feed(self, food)
4+ local condition = self:getCondition(CONDITION_REGENERATION, CONDITIONID_DEFAULT)
5+ if condition then
6+ condition:setTicks(condition:getTicks() + (food * 1000))
7+ else
8+ local vocation = self:getVocation()
9+ if not vocation then
10+ return nil
11+ end
12+
13+ foodCondition:setTicks(food * 1000)
14+ foodCondition:setParameter(CONDITION_PARAM_HEALTHGAIN, vocation:getHealthGainAmount())
15+ foodCondition:setParameter(CONDITION_PARAM_HEALTHTICKS, vocation:getHealthGainTicks() * 1000)
16+ foodCondition:setParameter(CONDITION_PARAM_MANAGAIN, vocation:getManaGainAmount())
17+ foodCondition:setParameter(CONDITION_PARAM_MANATICKS, vocation:getManaGainTicks() * 1000)
18+
19+ self:addCondition(foodCondition)
20+ end
21+ return true
22+end
23+
24+function Player.getClosestFreePosition(self, position, extended)
25+ if self:getGroup():getAccess() and self:getAccountType() >= ACCOUNT_TYPE_GOD then
26+ return position
27+ end
28+ return Creature.getClosestFreePosition(self, position, extended)
29+end
30+
31+function Player.getDepotItems(self, depotId)
32+ return self:getDepotChest(depotId, true):getItemHoldingCount()
33+end
34+
35+function Player.hasFlag(self, flag)
36+ return self:getGroup():hasFlag(flag)
37+end
38+
39+function Player.getLossPercent(self)
40+ local blessings = 0
41+ local lossPercent = {
42+ [0] = 100,
43+ [1] = 70,
44+ [2] = 45,
45+ [3] = 25,
46+ [4] = 10,
47+ [5] = 0
48+ }
49+
50+ for i = 1, 5 do
51+ if self:hasBlessing(i) then
52+ blessings = blessings + 1
53+ end
54+ end
55+ return lossPercent[blessings]
56+end
57+
58+function Player.isPremium(self)
59+ return self:getPremiumDays() > 0 or configManager.getBoolean(configKeys.FREE_PREMIUM)
60+end
61+
62+function Player.sendCancelMessage(self, message)
63+ if type(message) == "number" then
64+ message = Game.getReturnMessage(message)
65+ end
66+ return self:sendTextMessage(MESSAGE_STATUS_SMALL, message)
67+end
68+
69+function Player.isUsingOtClient(self)
70+ return self:getClient().os >= CLIENTOS_OTCLIENT_LINUX
71+end
72+
73+function Player.sendExtendedOpcode(self, opcode, buffer)
74+ if not self:isUsingOtClient() then
75+ return false
76+ end
77+
78+ local networkMessage = NetworkMessage()
79+ networkMessage:addByte(0x32)
80+ networkMessage:addByte(opcode)
81+ networkMessage:addString(buffer)
82+ networkMessage:sendToPlayer(self)
83+ networkMessage:delete()
84+ return true
85+end
86+
87+APPLY_SKILL_MULTIPLIER = true
88+local addSkillTriesFunc = Player.addSkillTries
89+function Player.addSkillTries(...)
90+ APPLY_SKILL_MULTIPLIER = false
91+ local ret = addSkillTriesFunc(...)
92+ APPLY_SKILL_MULTIPLIER = true
93+ return ret
94+end
95+
96+local addManaSpentFunc = Player.addManaSpent
97+function Player.addManaSpent(...)
98+ APPLY_SKILL_MULTIPLIER = false
99+ local ret = addManaSpentFunc(...)
100+ APPLY_SKILL_MULTIPLIER = true
101+ return ret
102+end
103+
104+-- Always pass the number through the isValidMoney function first before using the transferMoneyTo
105+function Player.transferMoneyTo(self, target, amount)
106+ if not target then
107+ return false
108+ end
109+
110+ -- See if you can afford this transfer
111+ local balance = self:getBankBalance()
112+ if amount > balance then
113+ return false
114+ end
115+
116+ -- See if player is online
117+ local targetPlayer = Player(target.guid)
118+ if targetPlayer then
119+ targetPlayer:setBankBalance(targetPlayer:getBankBalance() + amount)
120+ else
121+ db.query("UPDATE `players` SET `balance` = `balance` + " .. amount .. " WHERE `id` = '" .. target.guid .. "'")
122+ end
123+
124+ self:setBankBalance(self:getBankBalance() - amount)
125+ return true
126+end
127+
128+function Player.canCarryMoney(self, amount)
129+ -- Anyone can carry as much imaginary money as they desire
130+ if amount == 0 then
131+ return true
132+ end
133+
134+ -- The 3 below loops will populate these local variables
135+ local totalWeight = 0
136+ local inventorySlots = 0
137+
138+ -- Add crystal coins to totalWeight and inventorySlots
139+ local type_crystal = ItemType(ITEM_CRYSTAL_COIN)
140+ local crystalCoins = math.floor(amount / 10000)
141+ if crystalCoins > 0 then
142+ amount = amount - (crystalCoins * 10000)
143+ while crystalCoins > 0 do
144+ local count = math.min(100, crystalCoins)
145+ totalWeight = totalWeight + type_crystal:getWeight(count)
146+ crystalCoins = crystalCoins - count
147+ inventorySlots = inventorySlots + 1
148+ end
149+ end
150+
151+ -- Add platinum coins to totalWeight and inventorySlots
152+ local type_platinum = ItemType(ITEM_PLATINUM_COIN)
153+ local platinumCoins = math.floor(amount / 100)
154+ if platinumCoins > 0 then
155+ amount = amount - (platinumCoins * 100)
156+ while platinumCoins > 0 do
157+ local count = math.min(100, platinumCoins)
158+ totalWeight = totalWeight + type_platinum:getWeight(count)
159+ platinumCoins = platinumCoins - count
160+ inventorySlots = inventorySlots + 1
161+ end
162+ end
163+
164+ -- Add gold coins to totalWeight and inventorySlots
165+ local type_gold = ItemType(ITEM_GOLD_COIN)
166+ if amount > 0 then
167+ while amount > 0 do
168+ local count = math.min(100, amount)
169+ totalWeight = totalWeight + type_gold:getWeight(count)
170+ amount = amount - count
171+ inventorySlots = inventorySlots + 1
172+ end
173+ end
174+
175+ -- If player don't have enough capacity to carry this money
176+ if self:getFreeCapacity() < totalWeight then
177+ return false
178+ end
179+
180+ -- If player don't have enough available inventory slots to carry this money
181+ local backpack = self:getSlotItem(CONST_SLOT_BACKPACK)
182+ if not backpack or backpack:getEmptySlots(true) < inventorySlots then
183+ return false
184+ end
185+ return true
186+end
187+
188+function Player.withdrawMoney(self, amount)
189+ local balance = self:getBankBalance()
190+ if amount > balance or not self:addMoney(amount) then
191+ return false
192+ end
193+
194+ self:setBankBalance(balance - amount)
195+ return true
196+end
197+
198+function Player.depositMoney(self, amount)
199+ if not self:removeMoney(amount) then
200+ return false
201+ end
202+
203+ self:setBankBalance(self:getBankBalance() + amount)
204+ return true
205+end
206+
207+function Player.JumpCreature(self, creatureID, jumpHeight, jumpDuration, jumpStraight)
208+ local networkMessage = NetworkMessage()
209+ networkMessage:addByte(0x36) -- 54
210+ networkMessage:addU32(creatureID)
211+ networkMessage:addU32(jumpHeight)
212+ networkMessage:addU32(jumpDuration)
213+ networkMessage:addByte(jumpStraight)
214+ networkMessage:sendToPlayer(self)
215+ networkMessage:delete()
216+ return true
217+end
A data/lib/core/position.lua +81-0 View file
@@ -0,0 +1,81 @@
1+Position.directionOffset = {
2+ [DIRECTION_NORTH] = {x = 0, y = -1},
3+ [DIRECTION_EAST] = {x = 1, y = 0},
4+ [DIRECTION_SOUTH] = {x = 0, y = 1},
5+ [DIRECTION_WEST] = {x = -1, y = 0},
6+ [DIRECTION_SOUTHWEST] = {x = -1, y = 1},
7+ [DIRECTION_SOUTHEAST] = {x = 1, y = 1},
8+ [DIRECTION_NORTHWEST] = {x = -1, y = -1},
9+ [DIRECTION_NORTHEAST] = {x = 1, y = -1}
10+}
11+
12+function Position:getNextPosition(direction, steps)
13+ local offset = Position.directionOffset[direction]
14+ if offset then
15+ steps = steps or 1
16+ self.x = self.x + offset.x * steps
17+ self.y = self.y + offset.y * steps
18+ end
19+end
20+
21+function Position:moveUpstairs()
22+ local swap = function (lhs, rhs)
23+ lhs.x, rhs.x = rhs.x, lhs.x
24+ lhs.y, rhs.y = rhs.y, lhs.y
25+ lhs.z, rhs.z = rhs.z, lhs.z
26+ end
27+
28+ self.z = self.z - 1
29+
30+ local defaultPosition = self + Position.directionOffset[DIRECTION_SOUTH]
31+ local toTile = Tile(defaultPosition)
32+ if not toTile or not toTile:isWalkable() then
33+ for direction = DIRECTION_NORTH, DIRECTION_NORTHEAST do
34+ if direction == DIRECTION_SOUTH then
35+ direction = DIRECTION_WEST
36+ end
37+
38+ local position = self + Position.directionOffset[direction]
39+ toTile = Tile(position)
40+ if toTile and toTile:isWalkable() then
41+ swap(self, position)
42+ return self
43+ end
44+ end
45+ end
46+ swap(self, defaultPosition)
47+ return self
48+end
49+
50+function Position:isInRange(from, to)
51+ -- No matter what corner from and to is, we want to make
52+ -- life easier by calculating north-west and south-east
53+ local zone = {
54+ nW = {
55+ x = (from.x < to.x and from.x or to.x),
56+ y = (from.y < to.y and from.y or to.y),
57+ z = (from.z < to.z and from.z or to.z)
58+ },
59+ sE = {
60+ x = (to.x > from.x and to.x or from.x),
61+ y = (to.y > from.y and to.y or from.y),
62+ z = (to.z > from.z and to.z or from.z)
63+ }
64+ }
65+
66+ if self.x >= zone.nW.x and self.x <= zone.sE.x
67+ and self.y >= zone.nW.y and self.y <= zone.sE.y
68+ and self.z >= zone.nW.z and self.z <= zone.sE.z then
69+ return true
70+ end
71+ return false
72+end
73+
74+function Position:notifySummonAppear(summon)
75+ local spectators = Game.getSpectators(self)
76+ for _, spectator in ipairs(spectators) do
77+ if spectator:isMonster() and spectator ~= summon then
78+ spectator:addTarget(summon)
79+ end
80+ end
81+end
A data/lib/core/storages.lua +110-0 View file
@@ -0,0 +1,110 @@
1+--[[
2+Reserved storage ranges:
3+- 300000 to 301000+ reserved for achievements
4+- 20000 to 21000+ reserved for achievement progress
5+- 10000000 to 20000000 reserved for outfits and mounts on source
6+]]--
7+PlayerStorageKeys = {
8+ annihilatorReward = 30015,
9+ promotion = 30018,
10+ delayLargeSeaShell = 30019,
11+ firstRod = 30020,
12+ delayWallMirror = 30021,
13+ madSheepSummon = 30023,
14+ crateUsable = 30024,
15+ afflictedOutfit = 30026,
16+ afflictedPlagueMask = 30027,
17+ afflictedPlagueBell = 30028,
18+ nailCaseUseCount = 30031,
19+ swampDigging = 30032,
20+ insectoidCell = 30033,
21+ vortexTamer = 30034,
22+ achievementsBase = 300000,
23+ achievementsCounter = 20000,
24+ dungeonsDifficulty = 86597,
25+ archetype = 86600,
26+}
27+
28+STORAGEVALUE_ARCHETYPE = {
29+ ACTIVE = 86600,
30+ LEVEL = 86610,
31+ EXPERIENCE = 86620,
32+ SKILL_POINTS = 86630,
33+ SKILLS = 86700,
34+ SKILLS_OFFSET = 100,
35+ PASSIVES = 87700,
36+ PASSIVES_OFFSET = 100,
37+ RESOURCES = 88700,
38+ SECOND_LEARNED = 88710,
39+ THIRD_LEARNED = 88711,
40+ SKILL_POINTS_SPENT = 88712,
41+}
42+
43+GlobalStorageKeys = {
44+}
45+
46+Ships = {
47+ ScarletRaven = 37120,
48+ TheAwakener = 37121,
49+ TheCounterspell = 37122,
50+ TheExplorer = 37123,
51+ Whisperwind = 37124,
52+ MenethilMarch = 37125,
53+ Shadowmeld = 37126
54+}
55+
56+BoatMissions = {
57+ MagnusBlackWaterFirst = 37127,
58+ MagnusBlackWaterSecond = 37128,
59+ MagnusBlackWaterThird = 37129,
60+ MagnusBlackWaterFourth = 37130,
61+ MagnusBlackWaterFifth = 37131,
62+ MagnusBlackWaterSixth = 37132,
63+ ShipWrightTraderOne = 37133
64+}
65+
66+Battlepass = {
67+ firePanther = 39055,
68+ retroBoxOutfitWarrior = 39056,
69+ retroBoxOutfitSummoner = 39057,
70+ retroBoxOutfitNobleman = 39058,
71+ retroBoxOutfitMage = 39059,
72+ retroBoxOutfitKnight = 39060,
73+ retroBoxOutfitHunter = 39061,
74+ retroBoxOutfitCitizen = 39062,
75+ erectDraptor = 39063
76+}
77+
78+Jobs = {
79+ Mining = {
80+ SmallBronzeIngot = 73800,
81+ SmallSilverIngot = 73801,
82+ SmallSapphireIngot = 73802,
83+ SmallGoldIngot = 73803,
84+ SmallAmethystIngot = 73804,
85+ SmallRubyIngot = 73805,
86+ SmallEmeraldIngot = 73806,
87+ BigBronzeIngot = 73807,
88+ BigSilverIngot = 73808,
89+ BigSapphireIngot = 73809,
90+ BigGoldIngot = 73810,
91+ BigAmethystIngot = 73811,
92+ BigRubyIngot = 73812,
93+ BigEmeraldIngot = 73813,
94+ },
95+ Woodcutting = {
96+ BigPieceOfWood = 73814,
97+ AshPlank = 73815,
98+ ChestnutPlank = 73816,
99+ OlivePlank = 73817,
100+ CherryPlank = 73818
101+ },
102+ Herbalist = {
103+ NimbleLifePotion = 73819,
104+ HazelLifePotion = 73820,
105+ TumbleLifePotion = 73821,
106+ NimbleManaPotion = 73822,
107+ HazelManaPotion = 73823,
108+ TumbleManaPotion = 73824
109+ }
110+}
A data/lib/core/teleport.lua +3-0 View file
@@ -0,0 +1,3 @@
1+function Teleport.isTeleport(self)
2+ return true
3+end
A data/lib/core/tile.lua +55-0 View file
@@ -0,0 +1,55 @@
1+function Tile.isCreature(self)
2+ return false
3+end
4+
5+function Tile.isItem(self)
6+ return false
7+end
8+
9+function Tile.isTile(self)
10+ return true
11+end
12+
13+function Tile.isContainer(self)
14+ return false
15+end
16+
17+function Tile.relocateTo(self, toPosition)
18+ if self:getPosition() == toPosition or not Tile(toPosition) then
19+ return false
20+ end
21+
22+ for i = self:getThingCount() - 1, 0, -1 do
23+ local thing = self:getThing(i)
24+ if thing then
25+ if thing:isItem() then
26+ if thing:getFluidType() ~= 0 then
27+ thing:remove()
28+ elseif ItemType(thing:getId()):isMovable() then
29+ thing:moveTo(toPosition)
30+ end
31+ elseif thing:isCreature() then
32+ thing:teleportTo(toPosition)
33+ end
34+ end
35+ end
36+
37+ return true
38+end
39+
40+function Tile.isWalkable(self)
41+ local ground = self:getGround()
42+ if not ground or ground:hasProperty(CONST_PROP_BLOCKSOLID) then
43+ return false
44+ end
45+
46+ local items = self:getItems()
47+ for i = 1, self:getItemCount() do
48+ local item = items[i]
49+ local itemType = item:getType()
50+ if itemType:getType() ~= ITEM_TYPE_MAGICFIELD and not itemType:isMovable() and item:hasProperty(CONST_PROP_BLOCKSOLID) then
51+ return false
52+ end
53+ end
54+ return true
55+end
A data/lib/core/vocation.lua +7-0 View file
@@ -0,0 +1,7 @@
1+function Vocation.getBase(self)
2+ local base = self
3+ while base:getDemotion() do
4+ base = base:getDemotion()
5+ end
6+ return base
7+end
Top