illarion-content/item/lights.lua
2026-01-26 17:13:17 +01:00

409 lines
19 KiB
Lua
Raw Permalink Blame History

--[[
Illarion Server
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU Affero General Public License for more
details.
You should have received a copy of the GNU Affero General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
]]
local common = require("base.common")
local lookat = require("base.lookat")
local tutorial = require("content.tutorial")
local M = {}
local DEFAULT_WEAR = 60 -- default duration of 3 hours, 177-180 minutes based on when in the current cycle it was lit.
local LightsOff = {}
local LightsOn = {}
-- torch
LightsOff[Item.torch] = { on = 392 }
LightsOn[392] = { off = Item.torch, portable = true }
-- torch holder
LightsOff[401] = { on = 402, req = { id = 392, alternative = Item.torch, num = 1 } } -- facing south
LightsOn[402] = { off = 401}
LightsOff[403] = { on = 404, req = { id = 392, alternative = Item.torch, num = 1 } } -- facing west
LightsOn[404] = { off = 403}
-- candles
LightsOff[2853] = { on = 2851, req = { id = Item.candles, num = 3 } } -- facing south
LightsOn[2851] = { off = 2853 }
LightsOff[2854] = { on = 2852, req = { id = Item.candles, num = 3 } } -- facing west
LightsOn[2852] = { off = 2854 }
LightsOff[5232] = { on = 5233, req = { id = Item.candles, num = 3 } }
LightsOn[5233] = { off = 5232 }
-- candle
LightsOff[399] = { on = 400, req = { id = Item.candles, num = 1 } }
LightsOn[400] = { off = 399, portable = true }
-- oil lamp
LightsOff[92] = { on = 397, req = { id = Item.lampOil, num = 1, remnant = 390} }
LightsOn[397] = { off = 92, portable = true }
-- oil lamp holder
LightsOff[395] = { on = 396, req = { id = Item.lampOil, num = 1, remnant = 390 } }
LightsOn[396] = { off = 395 }
-- lantern
LightsOff[393] = { on = 394, req = { id = Item.candles, num = 1 } } -- black, portable
LightsOn[394] = { off = 393, portable = true }
LightsOff[2856] = { on = 2855, req = { id = Item.candles, num = 1 } } -- grey, static
LightsOn[2855] = { off = 2856 }
LightsOff[1127] = { on = 1123, req = {id = Item.candles, num = 1} } -- wall lantern
LightsOff[1123] = { off = 1127 }
LightsOff[1129] = { on = 1124, req = {id = Item.candles, num = 1} } -- wall lantern
LightsOff[1124] = { off = 1129 }
LightsOff[1128] = { on = 1125, req = {id = Item.candles, num = 1} } -- wall lantern
LightsOff[1125] = { off = 1128 }
LightsOff[1130] = { on = 1126, req = {id = Item.candles, num = 1} } -- wall lantern
LightsOff[1126] = { off = 1130 }
-- chandelier
LightsOff[Item.chandelierUnlit] = { on = Item.chandelier, req = {id = Item.candles, num = 10}}
LightsOn[Item.chandelier] = {off = Item.chandelierUnlit}
local ReqTexts = {}
ReqTexts.german = { [392] = "Fackeln", [Item.candles] = "Kerzen", [Item.lampOil] = "Lampen<EFBFBD>l" }
ReqTexts.english = { [392] = "torches", [Item.candles] = "candles", [Item.lampOil] = "lamp oil" }
local function lightTheItem(theItem, newWear)
theItem.id = LightsOff[theItem.id].on
theItem.wear = newWear
world:changeItem(theItem)
end
local function passesCheckForStackSizeAndPosition(user, sourceItem)
if sourceItem.id == Item.torch and sourceItem:getType() ~= scriptItem.inventory then
common.InformNLS(user,
"Nimm die Lichtquelle in die Hand.",
"Take the light source into your hand.")
return false
end
if sourceItem:getType() == scriptItem.container then
common.InformNLS(user,
"Nimm die Lichtquelle in die Hand oder lege sie am G<>rtel ab.",
"Take the light source into your hand or put it on your belt.")
return false
end
if sourceItem.number > 1 then
user:inform("Du kannst immer nur eine Lichtquelle auf einmal anz<6E>nden. Um einen Stapel aufzuteilen, halte die Umschalttaste, w<>hrend du den Stapel auf ein freies Inventarfeld ziehst.",
"You can only light up one light source at once. To split a stack, hold shift while dragging the item stack to a free inventory slot.")
return false
end
return true
end
local function checkIfRequirementsAreMetAndConsumeUnlitItem(user, unlitItem, sourceItem)
local litTorch = false
local rareness
if unlitItem.req then
-- there's a requirement, check on body and belt
local alternative = unlitItem.req.alternative
local hasEnoughAlternativeItem = false
if alternative then
hasEnoughAlternativeItem = user:countItemAt("body", alternative) + user:countItemAt("belt", alternative) >= unlitItem.req.num
end
local hasEnoughOfRequiredItem = user:countItemAt("body", unlitItem.req.id) + user:countItemAt("belt", unlitItem.req.id) >= unlitItem.req.num
for i = 1, 3 do
local alternateCheck
if alternative then
alternateCheck = user:countItemAt("body", alternative, {["rareness"] = i+1}) + user:countItemAt("belt", alternative, {["rareness"] = i+1}) >= unlitItem.req.num
end
local requiredCheck = user:countItemAt("body", unlitItem.req.id, {["rareness"] = i+1}) + user:countItemAt("belt", unlitItem.req.id, {["rareness"] = i+1}) >= unlitItem.req.num
if alternateCheck then
rareness = i+1
hasEnoughAlternativeItem = true
end
if requiredCheck then
hasEnoughOfRequiredItem = true
rareness = i+1
end
end
local itemToTake = unlitItem.req.id
if not hasEnoughOfRequiredItem and hasEnoughAlternativeItem then
itemToTake = alternative
end
if hasEnoughOfRequiredItem or hasEnoughAlternativeItem then
local myItem
local itemRest = unlitItem.req.num
for i=1,17 do
myItem = user:getItemAt( i )
if ( myItem.id == itemToTake ) and ((rareness and myItem:getData("rareness") == tostring(rareness)) or not rareness) then
world:erase( myItem, math.min( itemRest, myItem.number ) )
if itemToTake == 392 then
litTorch = myItem.wear
end
itemRest = itemRest - math.min( itemRest, myItem.number )
if itemRest == 0 then
break
end
end
end
if unlitItem.req.remnant then
common.CreateItem(user, unlitItem.req.remnant, unlitItem.req.num, 333, nil)
end
else
return false, litTorch, rareness
end
elseif not common.IsNilOrEmpty(sourceItem:getData("rareness")) then
rareness = tonumber(sourceItem:getData("rareness"))
end
return true, litTorch, rareness
end
local function handleUsageQuests(user, sourceItem) -- returns true if the quest handles the lighting process
--Tutorial Quest 330: Lighting a torch with NPC Henry Cunnigan
if user:getQuestProgress(330) == 2 and sourceItem.id == Item.torch and user:isInRangeToPosition((position (703,290,0)),20) then -- Only invoked if the user has the quest, has a torch and is in range of the NPC
user:setQuestProgress(330,3) -- Quest advanced when torch lit
common.InformNLS(user, tutorial.getTutorialInformDE("lights"), tutorial.getTutorialInformEN("lights"))
local Henry = common.getNpc(position(703,290,0),1,"Henry Cunnigan")
common.TalkNLS(Henry, Character.say, tutorial.getTutorialTalkDE("lights"), tutorial.getTutorialTalkEN("lights"))
return false
end
--Quest 105: NPC Gregor Remethar "A light at the end of the tunnel"
if sourceItem.id == 395 and (sourceItem.pos == position (873, 796, -3) or sourceItem.pos == position (873, 798, -3) ) and user:getQuestProgress(105) == 1 then
common.InformNLS(user, "[Queststatus] Du entfachst die Ehrenfeuer von Runewick. Kehre zu Gregor Remethar zur<75>ck, um deine Belohnung einzufordern.", "[Quest status] You lit the lights of honour of Runewick. Return to Gregor Remethar to claim your reward.")
user:setQuestProgress(105,2)
lightTheItem(sourceItem, math.random(20,60)) --these lights burn quite long
return true
end
return false
end
local function deleteParchmentsInHands(user, german, english, portable, sourceItem)
local leftHand = user:getItemAt(Character.left_tool)
local rightHand = user:getItemAt(Character.right_tool)
local burnt = false
local plural = false
if portable and not common.hasItemIdInHand(user, sourceItem.id) then
user:inform("Die "..german.." muss sich in deiner Hand befinden, damit du ein Pergament verbrennen kannst.", "The "..english.." must remain in your hands if you want to burn a parchment.")
return
end
if rightHand.id == Item.parchment then
world:erase(rightHand, rightHand.number)
burnt = true
if rightHand.number > 1 then
plural = true
end
end
if leftHand.id == Item.parchment then
world:erase(leftHand, leftHand.number)
if burnt or leftHand.number > 1 then
plural = true
end
burnt = true
end
local parchmentsEn = plural and "parchments" or "parchment"
local parchmentsDe = plural and "die Pergamente" or "das Pergament"
local pluralWasWhere = plural and "were" or "was"
local pluralWelcheWelchesDe = plural and "welche" or "welches"
local pluralBefandenBefandDe = plural and "befanden" or "befand"
if burnt then
user:inform("Du hast die "..german.." verwendet, um "..parchmentsDe.." zu verbrennen, "..pluralWelcheWelchesDe.." sich in deiner Hand "..pluralBefandenBefandDe..".", "You used the "..english.." to burn the "..parchmentsEn.." that "..pluralWasWhere.." in your hands.")
else
user:inform("In deinen H<>nden sind keine Pergamente. Hast du sie etwa verloren?", "No parchments were found in your hands. Did you lose them somewhere?")
end
end
local function burnParchmentConfirmation(user, german, english, portable, sourceItem)
local callback = function(dialog)
if not dialog:getSuccess() then
return
end
local selected = dialog:getSelectedIndex()+1
if selected == 1 then
deleteParchmentsInHands(user, german, english, portable, sourceItem)
else
return
end
end
local dialog = SelectionDialog(common.GetNLS(user,"Best<EFBFBD>tigung","Confirmation Check"), common.GetNLS(user,"Bist du sicher, dass du die Pergamente in deinen H<>nden verbrennen willst? Dies kann nicht r<>ckg<6B>ngig gemacht werden und wird alle Pergamente in deinen Handpl<70>tzen dauerhaft zerst<73>ren.", "Are you sure you want to burn the parchment(s) in your hands? This can not be undone and will permanently delete any parchments in your hand slots."), callback)
dialog:addOption(0,common.GetNLS(user,"Ja","Yes"))
dialog:addOption(0,common.GetNLS(user,"Nein, das ist Kunst.","No, I changed my mind."))
dialog:setCloseOnMove()
user:requestSelectionDialog(dialog)
end
function M.UseItem(user, sourceItem)
if sourceItem.id == Item.candles then
return
end
if not passesCheckForStackSizeAndPosition(user, sourceItem) then
return
end
local unlitItem = LightsOff[sourceItem.id]
local litItem = LightsOn[sourceItem.id]
if unlitItem then
local requirementsMet, litTorch, rareness = checkIfRequirementsAreMetAndConsumeUnlitItem(user, unlitItem, sourceItem)
if requirementsMet then
if not handleUsageQuests(user, sourceItem) then
-- no quests that already lit the item, proceeding with default
local wear = DEFAULT_WEAR
if rareness then
wear = wear + (rareness-1)*20 -- an hour extra wear per rarity
end
if litTorch then -- A lit torch will already have accounted for rareness and may have less wear remaining
wear = litTorch
end
lightTheItem(sourceItem, wear)
end
elseif unlitItem.req then
local number = ""
if unlitItem.req.num > 1 then
number = tostring(unlitItem.req.num).." "
end
common.InformNLS(user,
"Daf<EFBFBD>r brauchst du "..number.. ReqTexts.german[unlitItem.req.id] .. " in der Hand oder im G<>rtel.",
"You need "..number.. ReqTexts.english[unlitItem.req.id] .. " in your belt or hands to do that.")
end
elseif litItem then
if common.hasItemIdInHand(user, Item.parchment) then
local commonItem = world:getItemStatsFromId(sourceItem.id)
if litItem.portable and not common.IsItemInHands(sourceItem) then
user:inform("Um diese "..commonItem.German.." zum Verbrennen von Pergamenten zu verwenden, musst du sie in der Hand halten.", "To use this "..commonItem.English.." to burn a parchment, you must hold both in your hands.")
else
burnParchmentConfirmation(user, commonItem.German, commonItem.English, litItem.portable, sourceItem)
end
else
common.InformNLS(user,"Du verbrennst dir die Finger beim Versuch, das Feuer zu ersticken.","You burn your fingers while trying to extinguish the flames.")
end
end
end
function M.MoveItemAfterMove(user, itemBefore, itemAfter)
-- Quest 305: we burn a tobacco plantation
if user:getQuestProgress(305) == 2 then
if (itemAfter.pos.x >= 3) and (itemAfter.pos.x <= 6) and (itemAfter.pos.y >= 565) and (itemAfter.pos.y <= 571) and (itemAfter.pos.z <= 0) then
if LightsOn[itemBefore.id] then
local spawnFire = function(posi)
world:createItemFromId(359,1,posi,true,333,nil)
end
world:makeSound(7,position(5,568,0))
world:createItemFromId(359,1,position(5,568,0),true,333,nil)
common.CreateCircle(position(5,568,0), 1, spawnFire)
common.CreateCircle(position(5,568,0), 2, spawnFire)
user:setQuestProgress(305,3)
user:inform("Du hast das Tabakfeld zerst<73>rt. Gut gemacht. Spreche nun mit Tobis Vunu.","You destroyed the tobacco field. Well done. Talk to Tobis Vunu now.")
else
user:inform("Du kannst das Feld vermutlich mit brennenden Dingen besser abfackeln.","It's much easier to burn down a field if you are using fire.")
end
end
end
--Tutorial Quest 330: Equipping a torch with NPC Henry Cunnigan
if user:getQuestProgress(330)==1 and itemAfter.id==Item.torch and user:isInRangeToPosition((position (703,290,0)),20) and itemAfter:getType() == 4 then -- Only invoked if the user has the quest, has a torch and is in range of the NPC
user:setQuestProgress(330,2) --Quest advancement when torch equipped
local NPCList=world:getNPCSInRangeOf(position(703,290,0),1) --Let's be tolerant, the NPC might move a tile.
common.InformNLS(user, tutorial.getTutorialInformDE("lightsStart"), tutorial.getTutorialInformEN("lightsStart"))
for i, Henry in pairs(NPCList) do
common.TalkNLS(Henry, Character.say, tutorial.getTutorialTalkDE("lightsStart"), tutorial.getTutorialTalkEN("lightsStart"))
end
end
return true --leave safely
end
function M.LookAtItem(user, sourceItem)
local rareTexts = {
{value = 2, text = {english = "So uncommonly pristine, it is sure to burn longer than normal.", german = "So ungew<65>hnlich makellos, dass es sicher l<>nger als gew<65>hnlich brennt."}},
{value = 3, text = {english = "Of such rarely seen pristinity, it is sure to burn longer than normal.", german = "Von einer derart selten gesehenen Makellosigkeit, dass es sicher l<>nger als gew<65>hnlich brennt."}},
{value = 4, text = {english = "So uniquely pristine, it is sure to burn longer than normal, as if blessed by Br<42>gon himself.", german = "So einzigartig makellos, dass es sicher l<>nger als gew<65>hnlich brennt, als w<>re es von Br<42>gon selbst gesegnet."}}
}
local rareness, craftedRare = tonumber(sourceItem:getData("rareness")), sourceItem:getData("craftedRare")
local preText = {english = "", german = ""}
if craftedRare == "true" and rareness then
for _, rareText in pairs(rareTexts) do
if rareText.value == rareness then
preText.english = rareText.text.english
preText.german = rareText.text.german
if not sourceItem.id == Item.candles and not sourceItem.id == Item.lampOil then
preText.english = preText.english.."\n\n"
preText.german = preText.german.."\n\n"
end
end
end
end
if(LightsOn[sourceItem.id]) then
local TimeLeftI = sourceItem.wear
if(TimeLeftI == 255) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird nie ausbrennen.", preText.english.."It will never burn down.")
elseif (TimeLeftI == 0) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird sofort ausbrennen.", preText.english.."It will burn down immediately.")
elseif (TimeLeftI == 1) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird demn<6D>chst ausbrennen.", preText.english.."It will burn down anytime soon.")
elseif (TimeLeftI == 2) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird bald ausbrennen.", preText.english.."It will burn down soon.")
elseif (TimeLeftI <= 4) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird nach einer Weile ausbrennen.", preText.english.."It will burn down in a while.")
elseif (TimeLeftI <= DEFAULT_WEAR) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird nicht allzu bald ausbrennen.", preText.english.."It will not burn down anytime soon.")
elseif (TimeLeftI > DEFAULT_WEAR) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie wird nach langer Zeit ausbrennen.", preText.english.."It will burn down in a long time.")
end
elseif (LightsOff[sourceItem.id]) then
lookat.SetSpecialDescription(sourceItem, preText.german.."Sie ist nicht angez<65>ndet.", preText.english.."It is not lit, yet.")
else
lookat.SetSpecialDescription(sourceItem, preText.german, preText.english)
end
return lookat.GenerateLookAt(user, sourceItem, lookat.NONE, nil, nil, nil, true)
end
return M