· 4 years ago · Jul 11, 2021, 11:06 PM
1Blessings = {}
2
3Blessings.Credits = {
4 Developer = "Charles (Cjaker), DudZ",
5 Version = "2.0",
6 lastUpdate = "08/04/2020",
7 todo = {
8 "Insert & Select query in blessings_history",
9 "Add unfair fight reductio (convert the get killer is pvp fight with getDamageMap of dead player)",
10 "Gamestore buy blessing",
11 "Test ank print text",
12 "Test all functions",
13 "Test henricus prices/blessings",
14 "Add data \\movements\\scripts\\quests\\cults of tibia\\icedeath.lua blessing information",
15 "WotE data\\movements\\scripts\\quests\\wrath of the emperor\\realmTeleport.lua has line checking if player has bless 1??? wtf",
16 "add blessings module support npc\\lib\\npcsystem\\modules.lua",
17 "Fix store buying bless",
18 "Check if store is inside lua or source..."
19 }
20}
21
22Blessings.Config = {
23 AdventurerBlessingLevel = 0, -- Free full bless until level
24 HasToF = false, -- Enables/disables twist of fate
25 InquisitonBlessPriceMultiplier = 1.1, -- Bless price multiplied by henricus
26 SkulledDeathLoseStoreItem = true, -- Destroy all items on store when dying with red/blackskull
27 InventoryGlowOnFiveBless = true, -- Glow in yellow inventory items when the player has 5 or more bless,
28 Debug = false -- Prin debug messages in console if enabled
29}
30
31dofile('data/modules/scripts/blessings/assets.lua')
32
33--[=====[
34--
35-- Table structure `blessings_history`
36--
37
38CREATE TABLE IF NOT EXISTS `blessings_history` (
39 `id` int(11) NOT NULL,
40 `player_id` int(11) NOT NULL,
41 `blessing` tinyint(4) NOT NULL,
42 `loss` tinyint(1) NOT NULL,
43 `timestamp` int(11) NOT NULL,
44 CONSTRAINT `blessings_history_pk` PRIMARY KEY (`id`)
45) ENGINE=InnoDB DEFAULT CHARSET=latin1;
46
47--]=====]
48
49Blessings.DebugPrint = function(content, pre, pos)
50 if not Blessings.Config.Debug then
51 return
52 end
53 if pre == nil then
54 pre = ""
55 else
56 pre = pre.. " "
57 end
58 if pos == nil then
59 pos = ""
60 else
61 pos = " " .. pos
62 end
63 if type(content) == "boolean" then
64 Spdlog.debug(string.format("[Blessings] START BOOL - %s", pre))
65 Spdlog.debug(content)
66 Spdlog.debug(string.format("[Blessings] END BOOL - %s", pos))
67 else
68 Spdlog.debug(string.format("[Blessings] pre:[%s], content[%s], pos[%s]",
69 pre, content, pos))
70 end
71end
72
73Blessings.C_Packet = {
74 OpenWindow = 0xCF
75}
76
77Blessings.S_Packet = {
78 BlessDialog = 0x9B,
79 BlessStatus = 0x9C
80}
81
82function onRecvbyte(player, msg, byte)
83 if (byte == Blessings.C_Packet.OpenWindow) then
84 Blessings.sendBlessDialog(player)
85 end
86end
87
88Blessings.sendBlessStatus = function(player, curBless)
89 -- why not using ProtocolGame::sendBlessStatus ?
90 local msg = NetworkMessage()
91 msg:addByte(Blessings.S_Packet.BlessStatus)
92 callback = function(k) return true end
93 if curBless == nil then
94 curBless = player:getBlessings(callback) -- ex: {1, 2, 5, 7}
95 end
96 Blessings.DebugPrint(#curBless, "sendBlessStatus curBless")
97 local bitWiseCurrentBless = 0
98 local blessCount = 0
99
100 for i = 1, #curBless do
101 if curBless[i].losscount then
102 blessCount = blessCount + 1
103 end
104 if (not curBless[i].losscount and Blessings.Config.HasToF) or curBless[i].losscount then
105 bitWiseCurrentBless = bit.bor(bitWiseCurrentBless, Blessings.BitWiseTable[curBless[i].id])
106 end
107 end
108
109 if blessCount > 5 and Blessings.Config.InventoryGlowOnFiveBless then
110 bitWiseCurrentBless = bit.bor(bitWiseCurrentBless, 1)
111 end
112
113 msg:addU16(bitWiseCurrentBless)
114 msg:addByte(blessCount >= 7 and 3 or (blessCount > 0 and 2 or 1)) -- Bless dialog button colour 1 = Disabled | 2 = normal | 3 = green
115
116 -- if #curBless >= 5 then
117 -- msg:addU16(1) -- TODO ?
118 -- else
119 -- msg:addU16(0)
120 -- end
121
122 msg:sendToPlayer(player)
123end
124
125Blessings.sendBlessDialog = function(player)
126 -- TODO: Migrate to protocolgame.cpp
127 local msg = NetworkMessage()
128 msg:addByte(Blessings.S_Packet.BlessDialog)
129
130 callback = function(k) return true end
131 local curBless = player:getBlessings()
132
133 msg:addByte(Blessings.Config.HasToF and #Blessings.All or (#Blessings.All - 1)) -- total blessings
134 for k = 1, #Blessings.All do
135 v = Blessings.All[k]
136 if v.type ~= Blessings.Types.PvP or Blessings.Config.HasToF then
137 msg:addU16(Blessings.BitWiseTable[v.id])
138 msg:addByte(player:getBlessingCount(v.id))
139 msg:addByte(0) -- Store Blessings Count
140 end
141 end
142
143 local promotion = (player:isPremium() and player:isPromoted()) and 30 or 0
144 local PvPminXPLoss = Blessings.LossPercent[#curBless].skill + promotion
145 local PvPmaxXPLoss = PvPminXPLoss
146 if Blessings.Config.HasToF then
147 PvPmaxXPLoss = math.floor(PvPminXPLoss * 1.15)
148 end
149 local PvEXPLoss = PvPminXPLoss
150
151 local playerAmulet = player:getSlotItem(CONST_SLOT_NECKLACE)
152 local haveSkull = player:getSkull() >= 4
153 hasAol = (playerAmulet and playerAmulet:getId() == ITEM_AMULETOFLOSS)
154
155 equipLoss = Blessings.LossPercent[#curBless].item
156 if haveSkull then
157 equipLoss = 100
158 elseif hasAol then
159 equipLoss = 0
160 end
161
162 msg:addByte(2) -- BYTE PREMIUM (only work with premium days)
163 msg:addByte(promotion) -- XP Loss Lower POR SER PREMIUM
164 msg:addByte(PvPminXPLoss) -- XP/Skill loss min pvp death
165 msg:addByte(PvPmaxXPLoss) -- XP/Skill loss max pvp death
166 msg:addByte(PvEXPLoss) -- XP/Skill pve death
167 msg:addByte(equipLoss) -- Equip container lose pvp death
168 msg:addByte(equipLoss) -- Equip container pve death
169
170 msg:addByte(haveSkull and 1 or 0) -- is red/black skull
171 msg:addByte(hasAol and 1 or 0)
172
173
174 -- History
175 local historyAmount = 1
176 msg:addByte(historyAmount) -- History log count
177 for i = 1, historyAmount do
178 msg:addU32(os.time()) -- timestamp
179 msg:addByte(0) -- Color message (1 - Red | 0 = White loss)
180 msg:addString("Blessing Purchased") -- History message
181 end
182
183 msg:sendToPlayer(player)
184end
185
186Blessings.getBlessingsCost = function(level)
187 if level <= 30 then
188 return 2000
189 elseif level >= 120 then
190 return 20000
191 else
192 return (level - 20) * 200
193 end
194end
195
196Blessings.getPvpBlessingCost = function(level)
197 if level <= 30 then
198 return 2000
199 elseif level >= 270 then
200 return 50000
201 else
202 return (level - 20) * 200
203 end
204end
205
206Blessings.useCharm = function(player, item)
207 for index, value in pairs(Blessings.All) do
208 if item.itemid == value.charm then
209 if not value then
210 return true
211 end
212
213 if player:hasBlessing(value.id) then
214 player:say('You already possess this blessing.', TALKTYPE_MONSTER_SAY)
215 return true
216 end
217
218 player:addBlessing(value.id, 1)
219 player:sendTextMessage(MESSAGE_EVENT_ADVANCE, value.name .. ' protects you.')
220 player:getPosition():sendMagicEffect(CONST_ME_LOSEENERGY)
221 item:remove(1)
222 return true
223 end
224 end
225end
226
227Blessings.checkBless = function(player)
228 local result, bless = 'Received blessings:'
229 for k, v in pairs(Blessings.All) do
230 result = player:hasBlessing(k) and result .. '\n' .. v.name or result
231 end
232 player:sendTextMessage(MESSAGE_EVENT_ADVANCE, 20 > result:len() and 'No blessings received.' or result)
233 return true
234end
235
236Blessings.doAdventurerBlessing = function(player)
237 if player:getLevel() > Blessings.Config.AdventurerBlessingLevel then
238 return true
239 end
240 player:addMissingBless(true, true)
241
242 player:sendTextMessage(MESSAGE_EVENT_ADVANCE,'You received adventurers blessings for you being level lower than ' .. Blessings.Config.AdventurerBlessingLevel .. '!')
243 player:getPosition():sendMagicEffect(CONST_ME_HOLYDAMAGE)
244 return true
245end
246
247Blessings.getInquisitionPrice = function(player)
248 -- Find how many missing bless we have and give out the price
249 inquifilter = function(b) return b.inquisition end
250 donthavefilter = function(p, b) return not p:hasBlessing(b) end
251 local missing = #player:getBlessings(filter, donthavefilter)
252 local totalBlessPrice = Blessings.getBlessingsCost(player:getLevel()) * missing * Blessings.Config.InquisitonBlessPriceMultiplier
253 return missing, totalBlessPrice
254end
255
256Blessings.DropLoot = function(player, corpse, chance, skulled)
257 local multiplier = 100 -- Improve the loot randomness spectrum
258 math.randomseed(os.time()) -- Improve the loot randomness spectrum
259 chance = chance * multiplier
260 Blessings.DebugPrint(chance, "DropLoot chance")
261 for i = CONST_SLOT_HEAD, CONST_SLOT_AMMO do
262 local item = player:getSlotItem(i)
263 if item then
264 local thisChance = chance
265 local thisRandom = math.random(100*multiplier)
266 if not item:isContainer() then
267 thisChance = chance/10
268 end
269 Blessings.DebugPrint(thisChance/multiplier .. "%" .. " | thisRandom "..thisRandom/multiplier.."%", "DropLoot item "..item:getName() .. " |")
270 if skulled or thisRandom <= thisChance then
271 Blessings.DebugPrint("Dropped "..item:getName())
272 item:moveTo(corpse)
273 end
274 end
275 end
276 if skulled and Blessings.Config.SkulledDeathLoseStoreItem then
277 local inbox = player:getSlotItem(CONST_SLOT_STORE_INBOX)
278 local inboxsize = inbox:getSize() - 1
279 for i = 0, inboxsize do
280 inbox:getItem(i):destroy()
281 end
282 end
283end
284
285Blessings.ClearBless = function(player, killer, currentBless)
286 Blessings.DebugPrint(#currentBless, "ClearBless #currentBless")
287 local hasToF = Blessings.Config.HasToF and player:hasBlessing(1) or false
288 if hasToF and killer (killer:isPlayer() or (killer:getMaster() and killer:getMaster():isPlayer())) then -- TODO add better check if its pvp or pve
289 player:removeBlessing(1)
290 return
291 end
292 for i = 1, #currentBless do
293
294 Blessings.DebugPrint(i, "ClearBless curBless i", " | "..currentBless[i].name)
295 player:removeBlessing(currentBless[i].id, 1)
296 end
297end
298
299
300Blessings.BuyAllBlesses = function(player)
301 if not Tile(player:getPosition()):hasFlag(TILESTATE_PROTECTIONZONE) and (player:isPzLocked() or player:getCondition(CONDITION_INFIGHT, CONDITIONID_DEFAULT)) then
302 player:sendCancelMessage("You can't buy bless while in battle.")
303 player:getPosition():sendMagicEffect(CONST_ME_POFF)
304 return
305 end
306
307 local blessCost = Blessings.getBlessingsCost(player:getLevel())
308 local PvPBlessCost = Blessings.getPvpBlessingCost(player:getLevel())
309 local hasToF = Blessings.Config.HasToF and player:hasBlessing(1) or true
310 donthavefilter = function(p, b) return not p:hasBlessing(b) end
311 local missingBless = player:getBlessings(nil,donthavefilter)
312 local missingBlessAmt = #missingBless + (hasToF and 0 or 1)
313 local totalCost = blessCost * #missingBless
314
315 if missingBlessAmt == 0 then
316 player:sendCancelMessage("You are already blessed.")
317 player:getPosition():sendMagicEffect(CONST_ME_POFF)
318 return
319 end
320 if not hasToF then
321 totalCost = totalCost + PvPBlessCost
322 end
323
324 if player:removeMoneyNpc(totalCost) then
325 for i, v in ipairs(missingBless) do
326 player:addBlessing(v.id, 1)
327 end
328 player:sendCancelMessage("You received the remaining " .. missingBlessAmt .. " blesses for a total of " .. totalCost .." gold.")
329 player:getPosition():sendMagicEffect(CONST_ME_HOLYAREA)
330 else
331 player:sendCancelMessage("You don't have enough money. You need " .. totalCost .. " to buy all blesses.", cid)
332 player:getPosition():sendMagicEffect(CONST_ME_POFF)
333 end
334
335end
336
337Blessings.PlayerDeath = function(player, corpse, killer)
338 local hasToF = Blessings.Config.HasToF and player:hasBlessing(1) or false
339 local hasAol = (player:getSlotItem(CONST_SLOT_NECKLACE) and player:getSlotItem(CONST_SLOT_NECKLACE):getId() == ITEM_AMULETOFLOSS)
340 local haveSkull = isInArray({SKULL_RED, SKULL_BLACK}, player:getSkull())
341 local curBless = player:getBlessings()
342
343 if haveSkull then -- lose all bless + drop all items
344 Blessings.DropLoot(player, corpse, 100, true)
345 elseif #curBless < 5 and not hasAol then -- lose all items
346 local equipLoss = Blessings.LossPercent[#curBless].item
347 Blessings.DropLoot(player, corpse, equipLoss)
348 elseif #curBless < 5 and hasAol and not hasToF then
349 player:removeItem(ITEM_AMULETOFLOSS, 1, -1, false)
350 end
351 --Blessings.ClearBless(player, killer, curBless) IMPLEMENTED IN SOURCE BECAUSE THIS WAS HAPPENING BEFORE SKILL/EXP CALCULATIONS
352
353
354 if not player:getSlotItem(CONST_SLOT_BACKPACK) then
355 player:addItem(ITEM_BAG, 1, false, CONST_SLOT_BACKPACK)
356 end
357
358 return true
359end
360
361function Player.getBlessings(self, filter, hasblessingFilter)
362 local blessings = {}
363 if filter == nil then
364 filter = function(b) return b.losscount end
365 end
366
367 if hasblessingFilter == nil then
368 hasblessingFilter = function(p, b) return p:hasBlessing(b) end
369 end
370 for k, v in pairs(Blessings.All) do
371 if filter(v) and hasblessingFilter(self, k) then
372 table.insert(blessings, v)
373 end
374 end
375 return blessings
376end
377
378function Player.addMissingBless(self, all, tof)
379 if all == nil then
380 all = true
381 end
382 if tof == nil then
383 tof = false
384 elseif tof then
385 tof = tof and Blessings.Config.HasToF
386 end
387 for k, v in pairs(Blessings.All) do
388 if all or (v.type == Blessings.Types.REGULAR) or (tof and v.type == Blessings.Types.PvP) then
389 if not self:hasBlessing(k) then
390 self:addBlessing(k, 1)
391 end
392 end
393 end
394 Blessings.sendBlessStatus(self)
395end
396