mirror of
https://github.com/vcmi/vcmi.git
synced 2025-07-15 01:24:45 +02:00
Merge remote-tracking branch 'upstream/develop' into develop
This commit is contained in:
@ -13,6 +13,7 @@
|
||||
#include "../../lib/VCMI_Lib.h"
|
||||
#include "../../lib/CCreatureHandler.h"
|
||||
#include "../../lib/CHeroHandler.h"
|
||||
#include "../../lib/mapObjects/CompoundMapObjectID.h"
|
||||
#include "../../lib/mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../lib/mapObjects/CGHeroInstance.h"
|
||||
#include "../../lib/mapObjects/CGTownInstance.h"
|
||||
|
@ -79,7 +79,7 @@
|
||||
"vcmi.lobby.handicap.resource" : "Fornece aos jogadores recursos apropriados para começar, além dos recursos iniciais normais. Valores negativos são permitidos, mas são limitados a 0 no total (o jogador nunca começa com recursos negativos).",
|
||||
"vcmi.lobby.handicap.income" : "Altera as várias rendas do jogador em porcentagem. Arredondado para cima.",
|
||||
"vcmi.lobby.handicap.growth" : "Altera a taxa de produção das criaturas nas cidades possuídas pelo jogador. Arredondado para cima.",
|
||||
|
||||
|
||||
"vcmi.lobby.login.title" : "Sala de Espera Online do VCMI",
|
||||
"vcmi.lobby.login.username" : "Nome de usuário:",
|
||||
"vcmi.lobby.login.connecting" : "Conectando...",
|
||||
|
672
Mods/vcmi/config/vcmi/swedish.json
Normal file
672
Mods/vcmi/config/vcmi/swedish.json
Normal file
@ -0,0 +1,672 @@
|
||||
{
|
||||
"vcmi.adventureMap.monsterThreat.title" : "\n\nHotnivå: ",
|
||||
"vcmi.adventureMap.monsterThreat.levels.0" : "Utan ansträngning",
|
||||
"vcmi.adventureMap.monsterThreat.levels.1" : "Väldigt svag",
|
||||
"vcmi.adventureMap.monsterThreat.levels.2" : "Svag",
|
||||
"vcmi.adventureMap.monsterThreat.levels.3" : "Lite svagare",
|
||||
"vcmi.adventureMap.monsterThreat.levels.4" : "Jämbördig",
|
||||
"vcmi.adventureMap.monsterThreat.levels.5" : "Lite starkare",
|
||||
"vcmi.adventureMap.monsterThreat.levels.6" : "Stark",
|
||||
"vcmi.adventureMap.monsterThreat.levels.7" : "Väldigt stark",
|
||||
"vcmi.adventureMap.monsterThreat.levels.8" : "Utmanande",
|
||||
"vcmi.adventureMap.monsterThreat.levels.9" : "Överväldigande",
|
||||
"vcmi.adventureMap.monsterThreat.levels.10" : "Dödlig",
|
||||
"vcmi.adventureMap.monsterThreat.levels.11" : "Omöjlig",
|
||||
"vcmi.adventureMap.monsterLevel" : "\n\nNivå: %LEVEL - Faktion: %TOWN",
|
||||
|
||||
"vcmi.adventureMap.confirmRestartGame" : "Är du säker på att du vill starta om spelet?",
|
||||
"vcmi.adventureMap.noTownWithMarket" : "Det finns inga tillgängliga marknadsplatser!",
|
||||
"vcmi.adventureMap.noTownWithTavern" : "Det finns inga tillgängliga städer med värdshus!",
|
||||
"vcmi.adventureMap.spellUnknownProblem" : "Det finns ett okänt problem med den här formeln! Ingen mer information är tillgänglig.",
|
||||
"vcmi.adventureMap.playerAttacked" : "Spelare har blivit attackerad: %s",
|
||||
"vcmi.adventureMap.moveCostDetails" : "Förflyttningspoängs-kostnad: %TURNS tur(er) + %POINTS poäng - Återstående poäng: %REMAINING",
|
||||
"vcmi.adventureMap.moveCostDetailsNoTurns" : "Förflyttningspoängs-kostnad: %POINTS poäng - Återstående poäng: %REMAINING",
|
||||
"vcmi.adventureMap.movementPointsHeroInfo" : "(Förflyttningspoäng: %REMAINING / %POINTS)",
|
||||
"vcmi.adventureMap.replayOpponentTurnNotImplemented" : "Tyvärr, att spela om motståndarens tur är inte implementerat ännu!",
|
||||
|
||||
"vcmi.capitalColors.0" : "Röd",
|
||||
"vcmi.capitalColors.1" : "Blå",
|
||||
"vcmi.capitalColors.2" : "Ljusbrun",
|
||||
"vcmi.capitalColors.3" : "Grön",
|
||||
"vcmi.capitalColors.4" : "Orange",
|
||||
"vcmi.capitalColors.5" : "Lila",
|
||||
"vcmi.capitalColors.6" : "Grönblå",
|
||||
"vcmi.capitalColors.7" : "Rosa",
|
||||
|
||||
"vcmi.heroOverview.startingArmy" : "Startarmé",
|
||||
"vcmi.heroOverview.warMachine" : "Krigsmaskiner",
|
||||
"vcmi.heroOverview.secondarySkills" : "Sekundärförmågor",
|
||||
"vcmi.heroOverview.spells" : "Trollformler",
|
||||
|
||||
"vcmi.radialWheel.mergeSameUnit" : "Slå samman samma varelser",
|
||||
"vcmi.radialWheel.fillSingleUnit" : "Fyll på med enstaka varelser",
|
||||
"vcmi.radialWheel.splitSingleUnit" : "Dela av en enda varelse",
|
||||
"vcmi.radialWheel.splitUnitEqually" : "Dela upp varelser lika",
|
||||
"vcmi.radialWheel.moveUnit" : "Flytta varelser till en annan armé",
|
||||
"vcmi.radialWheel.splitUnit" : "Dela upp varelse till en annan ruta",
|
||||
|
||||
"vcmi.radialWheel.heroGetArmy" : "Hämta armé från annan hjälte",
|
||||
"vcmi.radialWheel.heroSwapArmy" : "Byt armé med annan hjälte",
|
||||
"vcmi.radialWheel.heroExchange" : "Öppna hjälteutbyte",
|
||||
"vcmi.radialWheel.heroGetArtifacts" : "Hämta artefakter från annan hjälte",
|
||||
"vcmi.radialWheel.heroSwapArtifacts" : "Byt artefakter med annan hjälte",
|
||||
"vcmi.radialWheel.heroDismiss" : "Avfärda hjälten",
|
||||
|
||||
"vcmi.radialWheel.moveTop" : "Flytta till toppen",
|
||||
"vcmi.radialWheel.moveUp" : "Flytta upp",
|
||||
"vcmi.radialWheel.moveDown" : "Flytta nedåt",
|
||||
"vcmi.radialWheel.moveBottom" : "Flytta till botten",
|
||||
|
||||
"vcmi.spellBook.search" : "sök...",
|
||||
|
||||
"vcmi.mainMenu.serverConnecting" : "Ansluter...",
|
||||
"vcmi.mainMenu.serverAddressEnter" : "Ange adress:",
|
||||
"vcmi.mainMenu.serverConnectionFailed" : "Misslyckades med att ansluta",
|
||||
"vcmi.mainMenu.serverClosing" : "Avslutar...",
|
||||
"vcmi.mainMenu.hostTCP" : "Spela som värd (TCP/IP)",
|
||||
"vcmi.mainMenu.joinTCP" : "Anslut till värd (TCP/IP)",
|
||||
|
||||
"vcmi.lobby.filepath" : "Filsökväg",
|
||||
"vcmi.lobby.creationDate" : "Skapelsedatum",
|
||||
"vcmi.lobby.scenarioName" : "Namn på scenariot",
|
||||
"vcmi.lobby.mapPreview" : "Förhandsgranskning av karta",
|
||||
"vcmi.lobby.noPreview" : "ingen förhandsgranskning",
|
||||
"vcmi.lobby.noUnderground" : "ingen underjord",
|
||||
"vcmi.lobby.sortDate" : "Sorterar kartor efter ändringsdatum",
|
||||
"vcmi.lobby.backToLobby" : "Återgå till lobbyn",
|
||||
"vcmi.lobby.author" : "Skaparen av lobbyn",
|
||||
"vcmi.lobby.handicap" : "Handikapp",
|
||||
"vcmi.lobby.handicap.resource" : "Ger spelarna lämpliga resurser att börja med utöver de normala startresurserna. Negativa värden är tillåtna men är begränsade till 0 totalt (spelaren börjar aldrig med negativa resurser).",
|
||||
"vcmi.lobby.handicap.income" : "Ändrar spelarens olika inkomster i procent (resultaten avrundas uppåt).",
|
||||
"vcmi.lobby.handicap.growth" : "Ändrar tillväxttakten för varelser i de städer som ägs av spelaren (resultaten avrundas uppåt).",
|
||||
|
||||
"vcmi.lobby.login.title" : "VCMI Online Lobby",
|
||||
"vcmi.lobby.login.username" : "Användarnamn:",
|
||||
"vcmi.lobby.login.connecting" : "Ansluter...",
|
||||
"vcmi.lobby.login.error" : "Anslutningsfel: %s",
|
||||
"vcmi.lobby.login.create" : "Nytt konto",
|
||||
"vcmi.lobby.login.login" : "Logga in",
|
||||
"vcmi.lobby.login.as" : "Logga in som %s",
|
||||
"vcmi.lobby.header.rooms" : "Spelrum - %d",
|
||||
"vcmi.lobby.header.channels" : "Chattkanaler",
|
||||
"vcmi.lobby.header.chat.global" : "Global spelchatt - %s", // %s -> språknamn
|
||||
"vcmi.lobby.header.chat.match" : "Chatt från föregående spel på %s", // %s -> datum och tid för spelstart
|
||||
"vcmi.lobby.header.chat.player" : "Privat chatt med %s", // %s -> smeknamn på en annan spelare
|
||||
"vcmi.lobby.header.history" : "Dina tidigare spel",
|
||||
"vcmi.lobby.header.players" : "Spelare online - %d",
|
||||
"vcmi.lobby.match.solo" : "Spel för en spelare",
|
||||
"vcmi.lobby.match.duel" : "Spel med %s", // %s -> smeknamn på en annan spelare
|
||||
"vcmi.lobby.match.multi" : "%d spelare",
|
||||
"vcmi.lobby.room.create" : "Skapa nytt rum",
|
||||
"vcmi.lobby.room.players.limit" : "Begränsning av spelare",
|
||||
"vcmi.lobby.room.description.public" : "Alla spelare kan gå med i det offentliga rummet.",
|
||||
"vcmi.lobby.room.description.private" : "Endast inbjudna spelare kan gå med i ett privat rum.",
|
||||
"vcmi.lobby.room.description.new" : "För att starta spelet, välj ett scenario eller skapa en slumpmässig karta.",
|
||||
"vcmi.lobby.room.description.load" : "Använd ett av dina sparade spel för att starta spelet.",
|
||||
"vcmi.lobby.room.description.limit" : "Upp till %d spelare kan komma in i ditt rum (dig inkluderad).",
|
||||
"vcmi.lobby.invite.header" : "Bjud in spelare",
|
||||
"vcmi.lobby.invite.notification" : "Spelaren har bjudit in dig till sitt spelrum. Du kan nu gå med i deras privata rum.",
|
||||
"vcmi.lobby.preview.title" : "Gå med i spelrummet",
|
||||
"vcmi.lobby.preview.subtitle" : "Spel på karta/RMG-mall: %s - Värdens smeknamn: %s", //TL Notering: 1) namn på karta eller RMG-mall 2) smeknamn på spelvärden
|
||||
"vcmi.lobby.preview.version" : "Spelversion:",
|
||||
"vcmi.lobby.preview.players" : "Spelare:",
|
||||
"vcmi.lobby.preview.mods" : "Moddar som används:",
|
||||
"vcmi.lobby.preview.allowed" : "Gå med i spelrummet?",
|
||||
"vcmi.lobby.preview.error.header" : "Det går inte att gå med i det här rummet.",
|
||||
"vcmi.lobby.preview.error.playing" : "Du måste lämna ditt nuvarande spel först.",
|
||||
"vcmi.lobby.preview.error.full" : "Rummet är redan fullt.",
|
||||
"vcmi.lobby.preview.error.busy" : "Rummet tar inte längre emot nya spelare.",
|
||||
"vcmi.lobby.preview.error.invite" : "Du blev inte inbjuden till det här rummet.",
|
||||
"vcmi.lobby.preview.error.mods" : "Du använder en annan uppsättning moddar.",
|
||||
"vcmi.lobby.preview.error.version" : "Du använder en annan version av VCMI.",
|
||||
"vcmi.lobby.room.new" : "Nytt spel",
|
||||
"vcmi.lobby.room.load" : "Ladda spel",
|
||||
"vcmi.lobby.room.type" : "Rumstyp",
|
||||
"vcmi.lobby.room.mode" : "Spelläge",
|
||||
"vcmi.lobby.room.state.public" : "Offentligt",
|
||||
"vcmi.lobby.room.state.private" : "Privat",
|
||||
"vcmi.lobby.room.state.busy" : "I spel",
|
||||
"vcmi.lobby.room.state.invited" : "Inbjuden",
|
||||
"vcmi.lobby.mod.state.compatible" : "Kompatibel",
|
||||
"vcmi.lobby.mod.state.disabled" : "Måste vara aktiverat",
|
||||
"vcmi.lobby.mod.state.version" : "Versioner matchar inte",
|
||||
"vcmi.lobby.mod.state.excessive" : "Måste vara inaktiverat",
|
||||
"vcmi.lobby.mod.state.missing" : "Ej installerad",
|
||||
"vcmi.lobby.pvp.coin.hover" : "Mynt",
|
||||
"vcmi.lobby.pvp.coin.help" : "Singla slant",
|
||||
"vcmi.lobby.pvp.randomTown.hover" : "Slumpmässig stad",
|
||||
"vcmi.lobby.pvp.randomTown.help" : "Skriv en slumpmässig stad i chatten",
|
||||
"vcmi.lobby.pvp.randomTownVs.hover" : "Slumpmässig stad vs.",
|
||||
"vcmi.lobby.pvp.randomTownVs.help" : "Skriv två slumpmässiga städer i chatten",
|
||||
"vcmi.lobby.pvp.versus" : "vs.",
|
||||
|
||||
"vcmi.client.errors.invalidMap" : "{Ogiltig karta eller kampanj}\n\nStartade inte spelet! Vald karta eller kampanj kan vara ogiltig eller skadad. Orsak:\n%s",
|
||||
"vcmi.client.errors.missingCampaigns" : "{Saknade datafiler}\n\nKampanjernas datafiler hittades inte! Du kanske använder ofullständiga eller skadade Heroes 3-datafiler. Vänligen installera om speldata.",
|
||||
"vcmi.server.errors.disconnected" : "{Nätverksfel}\n\nAnslutningen till spelservern har förlorats!",
|
||||
"vcmi.server.errors.existingProcess" : "En annan VCMI-serverprocess är igång. Vänligen avsluta den innan du startar ett nytt spel.",
|
||||
"vcmi.server.errors.modsToEnable" : "{Följande modd(ar) krävs}",
|
||||
"vcmi.server.errors.modsToDisable" : "{Följande modd(ar) måste inaktiveras}",
|
||||
"vcmi.server.errors.modNoDependency" : "Misslyckades med att ladda modd {'%s'}!\n Den är beroende av modd {'%s'} som inte är aktiverad!\n",
|
||||
"vcmi.server.errors.modConflict" : "Misslyckades med att ladda modd {'%s'}!\n Konflikter med aktiverad modd {'%s'}!\n",
|
||||
"vcmi.server.errors.unknownEntity" : "Misslyckades med att ladda sparat spel! Okänd enhet '%s' hittades i sparat spel! Sparningen kanske inte är kompatibel med den aktuella versionen av moddarna!",
|
||||
|
||||
"vcmi.dimensionDoor.seaToLandError" : "Det går inte att teleportera sig från hav till land eller tvärtom med trollformeln 'Dimensionsdörr'.",
|
||||
|
||||
"vcmi.settingsMainWindow.generalTab.hover" : "Allmänt",
|
||||
"vcmi.settingsMainWindow.generalTab.help" : "Växlar till fliken/menyn med allmänna spelklients-inställningar relaterade till allmänt beteende för spelklienten.",
|
||||
"vcmi.settingsMainWindow.battleTab.hover" : "Strid",
|
||||
"vcmi.settingsMainWindow.battleTab.help" : "Växlar till fliken/menyn med strids-inställningar där man kan konfigurera spelets beteende under strider.",
|
||||
"vcmi.settingsMainWindow.adventureTab.hover" : "Äventyrskarta",
|
||||
"vcmi.settingsMainWindow.adventureTab.help" : "Växlar till fliken/menyn med inställningar som har med äventyrskartan att göra (äventyrskartan är den del av spelet där spelarna kan styra sina hjältars förflyttning på land, vatten och nere i underjorden).",
|
||||
|
||||
"vcmi.systemOptions.videoGroup" : "Bild-inställningar",
|
||||
"vcmi.systemOptions.audioGroup" : "Ljud-inställningar",
|
||||
"vcmi.systemOptions.otherGroup" : "Andra inställningar", // unused right now
|
||||
"vcmi.systemOptions.townsGroup" : "By-/Stads-skärm",
|
||||
|
||||
"vcmi.statisticWindow.statistics" : "Statistik",
|
||||
"vcmi.statisticWindow.tsvCopy" : "Statistik-data till urklipp",
|
||||
"vcmi.statisticWindow.selectView" : "Välj vy",
|
||||
"vcmi.statisticWindow.value" : "Värde",
|
||||
"vcmi.statisticWindow.title.overview" : "Översikt",
|
||||
"vcmi.statisticWindow.title.resources" : "Resurser",
|
||||
"vcmi.statisticWindow.title.income" : "Inkomst",
|
||||
"vcmi.statisticWindow.title.numberOfHeroes" : "Antal hjältar",
|
||||
"vcmi.statisticWindow.title.numberOfTowns" : "Antal städer/byar",
|
||||
"vcmi.statisticWindow.title.numberOfArtifacts" : "Antal artefakter",
|
||||
"vcmi.statisticWindow.title.numberOfDwellings" : "Antal varelse-bon",
|
||||
"vcmi.statisticWindow.title.numberOfMines" : "Antal gruvor",
|
||||
"vcmi.statisticWindow.title.armyStrength" : "Arméns styrka",
|
||||
"vcmi.statisticWindow.title.experience" : "Erfarenhet",
|
||||
"vcmi.statisticWindow.title.resourcesSpentArmy" : "Armé-kostnader",
|
||||
"vcmi.statisticWindow.title.resourcesSpentBuildings" : "Byggnadskostnader",
|
||||
"vcmi.statisticWindow.title.mapExplored" : "Kart-utforskningsratio",
|
||||
"vcmi.statisticWindow.param.playerName" : "Spelarens namn",
|
||||
"vcmi.statisticWindow.param.daysSurvived" : "Överlevda dagar",
|
||||
"vcmi.statisticWindow.param.maxHeroLevel" : "Den högsta hjältenivån",
|
||||
"vcmi.statisticWindow.param.battleWinRatioHero" : "Vinstkvot (gentemot hjältar)",
|
||||
"vcmi.statisticWindow.param.battleWinRatioNeutral" : "Vinstkvot (gentemot neutrala)",
|
||||
"vcmi.statisticWindow.param.battlesHero" : "Strider (gentemot hjältar)",
|
||||
"vcmi.statisticWindow.param.battlesNeutral" : "Strider (gentemot neutrala)",
|
||||
"vcmi.statisticWindow.param.maxArmyStrength" : "Största totala arméstyrkan",
|
||||
"vcmi.statisticWindow.param.tradeVolume" : "Handelsvolym",
|
||||
"vcmi.statisticWindow.param.obeliskVisited" : "Obelisker besökta",
|
||||
"vcmi.statisticWindow.icon.townCaptured" : "Städer/byar erövrade",
|
||||
"vcmi.statisticWindow.icon.strongestHeroDefeated" : "Den starkaste motståndarhjälten som blivit besegrad",
|
||||
"vcmi.statisticWindow.icon.grailFound" : "Graal funnen",
|
||||
"vcmi.statisticWindow.icon.defeated" : "Besegrad",
|
||||
|
||||
"vcmi.systemOptions.fullscreenBorderless.hover" : "Helskärm (kantlös)",
|
||||
"vcmi.systemOptions.fullscreenBorderless.help" : "{Kantlös helskärm}\n\nI kantlöst helskärmsläge kommer spelet alltid att använda samma bildskärmsupplösning som valts i operativsystemet (bildskärmsupplösningen som valts i VCMI kommer ignoreras).",
|
||||
"vcmi.systemOptions.fullscreenExclusive.hover" : "Exklusivt helskärmsläge",
|
||||
"vcmi.systemOptions.fullscreenExclusive.help" : "{Helskärm}\n\nI exklusivt helskärmsläge kommer spelet att ändra bildskärmsupplösningen till det som valts i VCMI.",
|
||||
"vcmi.systemOptions.resolutionButton.hover" : "Bildskärmsupplösning: %wx%h",
|
||||
"vcmi.systemOptions.resolutionButton.help" : "{Bildskärmsupplösning}\n\nÄndrar bildskärmens upplösning i spelet.",
|
||||
"vcmi.systemOptions.resolutionMenu.hover" : "Välj bildskärmsupplösningen i spelet",
|
||||
"vcmi.systemOptions.resolutionMenu.help" : "Ändrar bildskärmsupplösning i spelet.",
|
||||
"vcmi.systemOptions.scalingButton.hover" : "Gränssnittsskalning: %p%",
|
||||
"vcmi.systemOptions.scalingButton.help" : "{Gränssnittsskalning}\n\nÄndrar storleken av de olika gränssnitten som finns i spelet.",
|
||||
"vcmi.systemOptions.scalingMenu.hover" : "Välj gränssnittsskalning",
|
||||
"vcmi.systemOptions.scalingMenu.help" : "Förstorar eller förminskar olika gränssnitt i spelet.",
|
||||
"vcmi.systemOptions.longTouchButton.hover" : "Fördröjt tryckintervall: %d ms", // Översättningsnot: ’ms’ = "millisekunder"
|
||||
"vcmi.systemOptions.longTouchButton.help" : "{Fördröjt tryckintervall}\n\nNär du använder dig av en pekskärm och vill komma åt spelalternativ behöver du göra en fördröjd pekskärmsberöring under en specifikt angiven tid (i millisekunder) för att en popup-meny skall synas.",
|
||||
"vcmi.systemOptions.longTouchMenu.hover" : "Välj tidsintervall för fördröjd pekskärmsberöringsmeny",
|
||||
"vcmi.systemOptions.longTouchMenu.help" : "Ändra varaktighetsintervallet för fördröjd beröring.",
|
||||
"vcmi.systemOptions.longTouchMenu.entry" : "%d millisekunder",
|
||||
"vcmi.systemOptions.framerateButton.hover" : "Visar FPS (skärmbilder per sekund)",
|
||||
"vcmi.systemOptions.framerateButton.help" : "{Visa FPS}\n\nVisar räknaren för bildrutor per sekund i hörnet av spelfönstret.",
|
||||
"vcmi.systemOptions.hapticFeedbackButton.hover" : "Haptisk återkoppling",
|
||||
"vcmi.systemOptions.hapticFeedbackButton.help" : "{Haptisk feedback}\n\nÄndrar den haptiska feedbacken för berörings-input.",
|
||||
"vcmi.systemOptions.enableUiEnhancementsButton.hover" : "Förbättringar av användargränssnittet",
|
||||
"vcmi.systemOptions.enableUiEnhancementsButton.help" : "{Gränssnittsförbättringar}\n\nVälj mellan olika förbättringar av användargränssnittet. Till exempel en lättåtkomlig ryggsäcksknapp med mera. Avaktivera för att få en mer klassisk spelupplevelse.",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.hover" : "Stor trollformelsbok",
|
||||
"vcmi.systemOptions.enableLargeSpellbookButton.help" : "{Stor trollformelsbok}\n\nAktiverar en större trollformelsbok som rymmer fler trollformler per sida (animeringen av sidbyte i den större trollformelsboken fungerar inte).",
|
||||
"vcmi.systemOptions.audioMuteFocus.hover" : "Stänger av ljudet vid inaktivitet",
|
||||
"vcmi.systemOptions.audioMuteFocus.help" : "{Stäng av ljud vid inaktivitet}\n\nStänger av ljudet i spelet vid inaktivitet. Undantag är meddelanden i spelet och ljudet för ny tur/omgång.",
|
||||
|
||||
"vcmi.adventureOptions.infoBarPick.hover" : "Visar textmeddelanden i infopanelen",
|
||||
"vcmi.adventureOptions.infoBarPick.help" : "{Infopanelsmeddelanden}\n\nNär det är möjligt kommer spelmeddelanden från besökande kartobjekt att visas i infopanelen istället för att dyka upp i ett separat fönster.",
|
||||
"vcmi.adventureOptions.numericQuantities.hover" : "Numeriska antal varelser",
|
||||
"vcmi.adventureOptions.numericQuantities.help" : "{Numerisk varelsemängd}\n\nVisa ungefärliga mängder av fiendevarelser i det numeriska A-B-formatet.",
|
||||
"vcmi.adventureOptions.forceMovementInfo.hover" : "Visa alltid förflyttningskostnad",
|
||||
"vcmi.adventureOptions.forceMovementInfo.help" : "{Visa alltid förflyttningskostnad}\n\nVisar alltid förflyttningspoäng i statusfältet (istället för att bara visa dem när du håller ned ALT-tangenten).",
|
||||
"vcmi.adventureOptions.showGrid.hover" : "Visa rutnät",
|
||||
"vcmi.adventureOptions.showGrid.help" : "{Visa rutnät}\n\nVisa rutnätsöverlägget som markerar gränserna mellan äventyrskartans brickor/rutor.",
|
||||
"vcmi.adventureOptions.borderScroll.hover" : "Kantrullning",
|
||||
"vcmi.adventureOptions.borderScroll.help" : "{Kantrullning}\n\nRullar äventyrskartan när markören är angränsande till fönsterkanten. Kan inaktiveras genom att hålla ned CTRL-tangenten.",
|
||||
"vcmi.adventureOptions.infoBarCreatureManagement.hover" : "Hantering av varelser i infopanelen i nedre högra hörnet",
|
||||
"vcmi.adventureOptions.infoBarCreatureManagement.help" : "{Varelsehantering i infopanelen}\n\nTillåter omarrangering av varelser i infopanelen längst ner till höger på äventyrskartan istället för att bläddra mellan olika infopaneler.",
|
||||
"vcmi.adventureOptions.leftButtonDrag.hover" : "Dra kartan med vänster musknapp",
|
||||
"vcmi.adventureOptions.leftButtonDrag.help" : "{Vänsterklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med vänster musknapp nedtryckt.",
|
||||
"vcmi.adventureOptions.rightButtonDrag.hover" : "Dra kartan med höger musknapp",
|
||||
"vcmi.adventureOptions.rightButtonDrag.help" : "{Högerklicksdragning}\n\nVid aktivering kan äventyrskartans kartvy dras genom att flytta musen med höger musknapp nedtryckt.",
|
||||
"vcmi.adventureOptions.smoothDragging.hover" : "Mjuk kartdragning",
|
||||
"vcmi.adventureOptions.smoothDragging.help" : "{Mjuk kartdragning}\n\nVid aktivering så har kartdragningen en modern rullningseffekt.",
|
||||
"vcmi.adventureOptions.skipAdventureMapAnimations.hover" : "Skippar övertoningseffekter",
|
||||
"vcmi.adventureOptions.skipAdventureMapAnimations.help" : "{Skippa övertoningseffekter}\n\nHoppar över ut- och intoningseffekten och liknande effekter av kartobjekt (resursinsamling, ombordning och avbordning av skepp osv.). Gör användargränssnittet mer reaktivt i vissa fall på bekostnad av estetiken. Speciellt användbart i PvP-spel. När maximal förflyttningshastighet är valt så är skippning av effekter aktiverat oavsett vad du har valt här.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.hover" : "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.hover" : "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.hover" : "",
|
||||
"vcmi.adventureOptions.mapScrollSpeed1.help" : "Ställ in kartans rullningshastighet på 'Mycket långsam'.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed5.help" : "Ställ in kartans rullningshastighet på 'Mycket snabb'.",
|
||||
"vcmi.adventureOptions.mapScrollSpeed6.help" : "Ställ in kartans rullningshastighet till 'Omedelbar'.",
|
||||
"vcmi.adventureOptions.hideBackground.hover" : "Dölj bakgrund",
|
||||
"vcmi.adventureOptions.hideBackground.help" : "{Dölj bakgrund}\n\nDöljer äventyrskartan i bakgrunden och visar en textur istället.",
|
||||
|
||||
"vcmi.battleOptions.queueSizeLabel.hover" : "Visa turordningskö",
|
||||
"vcmi.battleOptions.queueSizeNoneButton.hover" : "AV",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.hover" : "AUTO",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.hover" : "LITEN",
|
||||
"vcmi.battleOptions.queueSizeBigButton.hover" : "STOR",
|
||||
"vcmi.battleOptions.queueSizeNoneButton.help" : "Visa inte turordningskö.",
|
||||
"vcmi.battleOptions.queueSizeAutoButton.help" : "Justera automatiskt storleken på turordningskön baserat på spelets skärmbildsupplösning ('LITEN' används när det är mindre än 700 pixlar i höjd, 'STOR' används annars).",
|
||||
"vcmi.battleOptions.queueSizeSmallButton.help" : "Ställer in storleksinställningen på turordningskön till 'LITEN'.",
|
||||
"vcmi.battleOptions.queueSizeBigButton.help" : "Ställer in storleksinställningen på turordningskön till 'STOR' (bildskärmsupplösningen måste överstiga 700 pixlar i höjd).",
|
||||
"vcmi.battleOptions.animationsSpeed1.hover" : "",
|
||||
"vcmi.battleOptions.animationsSpeed5.hover" : "",
|
||||
"vcmi.battleOptions.animationsSpeed6.hover" : "",
|
||||
"vcmi.battleOptions.animationsSpeed1.help" : "Ställ in animationshastigheten till mycket långsam.",
|
||||
"vcmi.battleOptions.animationsSpeed5.help" : "Ställ in animationshastigheten till mycket snabb.",
|
||||
"vcmi.battleOptions.animationsSpeed6.help" : "Ställ in animationshastigheten till omedelbar.",
|
||||
"vcmi.battleOptions.movementHighlightOnHover.hover" : "Muspeka (hovra) för att avslöja förflyttningsräckvidd",
|
||||
"vcmi.battleOptions.movementHighlightOnHover.help" : "{Muspeka för att avslöja förflyttningsräckvidd}\n\nVisar enheters potentiella förflyttningsräckvidd över slagfältet när du håller muspekaren över dem.",
|
||||
"vcmi.battleOptions.rangeLimitHighlightOnHover.hover": "Avslöja skyttars räckvidd",
|
||||
"vcmi.battleOptions.rangeLimitHighlightOnHover.help" : "{Muspeka för att avslöja skyttars räckvidd}\n\nVisar hur långt en enhets distansattack sträcker sig över slagfältet när du håller muspekaren över dem.",
|
||||
"vcmi.battleOptions.showStickyHeroInfoWindows.hover" : "Visa fönster med hjältars primärförmågor",
|
||||
"vcmi.battleOptions.showStickyHeroInfoWindows.help" : "{Visa fönster med hjältars primärförmågor}\n\nKommer alltid att visa ett fönster där du kan se dina hjältars primärförmågor (anfall, försvar, trollkonst, kunskap och trollformelpoäng).",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.hover" : "Hoppa över intromusik",
|
||||
"vcmi.battleOptions.skipBattleIntroMusic.help" : "{Hoppa över intromusik}\n\nTillåt åtgärder under intromusiken som spelas i början av varje strid.",
|
||||
"vcmi.battleOptions.endWithAutocombat.hover" : "Slutför striden så fort som möjligt",
|
||||
"vcmi.battleOptions.endWithAutocombat.help" : "{Slutför strid}\n\nAuto-strid spelar striden åt dig för att striden ska slutföras så fort som möjligt.",
|
||||
"vcmi.battleOptions.showQuickSpell.hover" : "Snabb åtkomst till dina trollformler",
|
||||
"vcmi.battleOptions.showQuickSpell.help" : "{Visa snabbtrollformels-panelen}\n\nVisar en snabbvalspanel vid sidan av stridsfönstret där du har snabb åtkomst till några av dina trollformler",
|
||||
|
||||
"vcmi.adventureMap.revisitObject.hover" : "Gör ett återbesök",
|
||||
"vcmi.adventureMap.revisitObject.help" : "{Återbesök kartobjekt}\n\nEn hjälte som för närvarande står på ett kartobjekt kan göra ett återbesök (utan att först behöva avlägsna sig från kartobjektet för att sedan göra ett återbesök).",
|
||||
|
||||
"vcmi.battleWindow.pressKeyToSkipIntro" : "Tryck på valfri tangent för att starta striden omedelbart",
|
||||
"vcmi.battleWindow.damageEstimation.melee" : "Attackera %CREATURE (%DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.meleeKills" : "Attackera %CREATURE (%DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.ranged" : "Skjut %CREATURE (%SHOTS, %DAMAGE).",
|
||||
"vcmi.battleWindow.damageEstimation.rangedKills" : "Skjut %CREATURE (%SHOTS, %DAMAGE, %KILLS).",
|
||||
"vcmi.battleWindow.damageEstimation.shots" : "%d skott kvar",
|
||||
"vcmi.battleWindow.damageEstimation.shots.1" : "%d skott kvar",
|
||||
"vcmi.battleWindow.damageEstimation.damage" : "%d skada",
|
||||
"vcmi.battleWindow.damageEstimation.damage.1" : "%d skada",
|
||||
"vcmi.battleWindow.damageEstimation.kills" : "%d kommer att förgås",
|
||||
"vcmi.battleWindow.damageEstimation.kills.1" : "%d kommer att förgås",
|
||||
|
||||
"vcmi.battleWindow.damageRetaliation.will" : "Kommer att retaliera ",
|
||||
"vcmi.battleWindow.damageRetaliation.may" : "Kommer kanske att retaliera ",
|
||||
"vcmi.battleWindow.damageRetaliation.never" : "Kommer inte att retaliera.",
|
||||
"vcmi.battleWindow.damageRetaliation.damage" : "(%DAMAGE).",
|
||||
"vcmi.battleWindow.damageRetaliation.damageKills" : "(%DAMAGE, %KILLS).",
|
||||
|
||||
"vcmi.battleWindow.killed" : "Dödad",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.0" : "%d %s dödades av träffsäkra skott!",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.1" : "%d %s dödades med ett träffsäkert skott!",
|
||||
"vcmi.battleWindow.accurateShot.resultDescription.2" : "%d %s dödades av träffsäkra skott!",
|
||||
"vcmi.battleWindow.endWithAutocombat" : "Är du säker på att du vill slutföra striden med auto-strid?",
|
||||
|
||||
"vcmi.battleResultsWindow.applyResultsLabel" : "Acceptera stridsresultat?",
|
||||
|
||||
"vcmi.tutorialWindow.title" : "Pekskärmsintroduktion",
|
||||
"vcmi.tutorialWindow.decription.RightClick" : "Rör vid och håll kvar det element som du vill högerklicka på. Tryck på det fria området för att stänga.",
|
||||
"vcmi.tutorialWindow.decription.MapPanning" : "Tryck och dra med ett finger för att flytta kartan.",
|
||||
"vcmi.tutorialWindow.decription.MapZooming" : "Nyp med två fingrar för att ändra kartans zoom.",
|
||||
"vcmi.tutorialWindow.decription.RadialWheel" : "Genom att svepa öppnas ett radiellt hjul för olika åtgärder t.ex. hantering av varelser/hjältar och stadsåtgärder.",
|
||||
"vcmi.tutorialWindow.decription.BattleDirection" : "För att attackera från en viss riktning sveper du i den riktning från vilken attacken ska göras.",
|
||||
"vcmi.tutorialWindow.decription.BattleDirectionAbort" : "Gesten för attackriktning kan avbrytas om du sveper tillräckligt långt bort.",
|
||||
"vcmi.tutorialWindow.decription.AbortSpell" : "Tryck och håll för att avbryta en vald trollformel.",
|
||||
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.hover" : "Visar tillgängliga varelser att rekrytera",
|
||||
"vcmi.otherOptions.availableCreaturesAsDwellingLabel.help" : "{Visa tillgängliga varelser}\n\nVisa antalet varelser som finns tillgängliga att rekrytera istället för deras veckovisa förökning i stadsöversikten (nedre vänstra hörnet av stadsskärmen).",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.hover" : "Visar den veckovisa varelseförökningen",
|
||||
"vcmi.otherOptions.creatureGrowthAsDwellingLabel.help" : "{Visa veckovis varelseförökning}\n\nVisa varelsers veckovisa förökning istället för antalet tillgängliga varelser i stadsöversikten (nedre vänstra hörnet av stadsskärmen).",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.hover" : "Visa mindre varelse-info",
|
||||
"vcmi.otherOptions.compactTownCreatureInfo.help" : "{Kompakt varelse-info}\n\nVisa mindre information för stadens varelser i stadsöversikten (nedre vänstra hörnet av stadsskärmen).",
|
||||
|
||||
"vcmi.townHall.missingBase" : "Basbyggnaden '%s' måste byggas först",
|
||||
"vcmi.townHall.noCreaturesToRecruit" : "Det finns inga varelser att rekrytera!",
|
||||
|
||||
"vcmi.townStructure.bank.borrow" : "Du går in i banken. En bankman ser dig och säger: \"Vi har gjort ett specialerbjudande till dig. Du kan ta ett lån på 2500 guld från oss i 5 dagar. Du måste återbetala 500 guld varje dag.\"",
|
||||
"vcmi.townStructure.bank.payBack" : "Du går in i banken. En bankman ser dig och säger: \"Du har redan fått ditt lån. Betala tillbaka det innan du tar ett nytt.\"",
|
||||
|
||||
"vcmi.logicalExpressions.anyOf" : "Något av följande:",
|
||||
"vcmi.logicalExpressions.allOf" : "Alla följande:",
|
||||
"vcmi.logicalExpressions.noneOf" : "Inget av följande:",
|
||||
|
||||
"vcmi.heroWindow.openCommander.hover" : "Öppna befälhavarens informationsfönster",
|
||||
"vcmi.heroWindow.openCommander.help" : "Visar detaljer om befälhavaren för den här hjälten.",
|
||||
"vcmi.heroWindow.openBackpack.hover" : "Öppna artefaktryggsäcksfönster",
|
||||
"vcmi.heroWindow.openBackpack.help" : "Öppnar fönster som gör det enklare att hantera artefaktryggsäcken.",
|
||||
|
||||
"vcmi.tavernWindow.inviteHero" : "Bjud in hjälte",
|
||||
|
||||
"vcmi.commanderWindow.artifactMessage" : "Vill du återlämna denna artefakt till hjälten?",
|
||||
|
||||
"vcmi.creatureWindow.showBonuses.hover" : "Byt till bonusvy",
|
||||
"vcmi.creatureWindow.showBonuses.help" : "Visa befälhavarens aktiva bonusar.",
|
||||
"vcmi.creatureWindow.showSkills.hover" : "Byt till färdighetsvy",
|
||||
"vcmi.creatureWindow.showSkills.help" : "Visa befälhavarens inlärda färdigheter.",
|
||||
"vcmi.creatureWindow.returnArtifact.hover" : "Återlämna artefakt",
|
||||
"vcmi.creatureWindow.returnArtifact.help" : "Klicka på den här knappen för att lägga tillbaka artefakten i hjältens ryggsäck.",
|
||||
|
||||
"vcmi.questLog.hideComplete.hover" : "Gömmer alla slutförda uppdrag",
|
||||
"vcmi.questLog.hideComplete.help" : "Dölj alla slutförda uppdrag.",
|
||||
|
||||
"vcmi.randomMapTab.widgets.randomTemplate" : "(Slumpmässig)",
|
||||
"vcmi.randomMapTab.widgets.templateLabel" : "Mall",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsButton" : "Ställ in...",
|
||||
"vcmi.randomMapTab.widgets.teamAlignmentsLabel" : "Laggruppering",
|
||||
"vcmi.randomMapTab.widgets.roadTypesLabel" : "Vägtyper",
|
||||
|
||||
"vcmi.optionsTab.turnOptions.hover" : "Turomgångsalternativ",
|
||||
"vcmi.optionsTab.turnOptions.help" : "Välj alternativ för turomgångs-timer och simultana turer",
|
||||
|
||||
"vcmi.optionsTab.chessFieldBase.hover" : "Bas-timern",
|
||||
"vcmi.optionsTab.chessFieldTurn.hover" : "Turomgångs-timern",
|
||||
"vcmi.optionsTab.chessFieldBattle.hover" : "Strids-timern",
|
||||
"vcmi.optionsTab.chessFieldUnit.hover" : "Enhets-timern",
|
||||
"vcmi.optionsTab.chessFieldBase.help" : "Används när {Turomgångs-timern} når '0'. Ställs in en gång i början av spelet. När den når '0' avslutas den aktuella turomgången (pågående strid avslutas med förlust).",
|
||||
"vcmi.optionsTab.chessFieldTurnAccumulate.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid läggs till i {Bas-timern} till nästa turomgång.",
|
||||
"vcmi.optionsTab.chessFieldTurnDiscard.help" : "Används utanför strid eller när {Strids-timern} tar slut. Återställs varje turomgång. Outnyttjad tid går förlorad.",
|
||||
"vcmi.optionsTab.chessFieldBattle.help" : "Används i strider med AI eller i PVP-strid när {Enhets-timern} tar slut. Återställs i början av varje strid.",
|
||||
"vcmi.optionsTab.chessFieldUnitAccumulate.help" : "Används när du styr din enhet i PVP-strid. Outnyttjad tid läggs till i {Strids-timern} när enheten har avslutat sin turomgång.",
|
||||
"vcmi.optionsTab.chessFieldUnitDiscard.help" : "Används när du styr din enhet i PVP-strid. Återställs i början av varje enhets turomgång. Outnyttjad tid går förlorad.",
|
||||
|
||||
"vcmi.optionsTab.accumulate" : "Ackumulera",
|
||||
|
||||
"vcmi.optionsTab.simturnsTitle" : "Simultana turomgångar",
|
||||
"vcmi.optionsTab.simturnsMin.hover" : "Åtminstone i",
|
||||
"vcmi.optionsTab.simturnsMax.hover" : "Som mest i",
|
||||
"vcmi.optionsTab.simturnsAI.hover" : "(Experimentell) Simultana AI-turomgångar",
|
||||
"vcmi.optionsTab.simturnsMin.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar. Kontakter mellan spelare under denna period är blockerade",
|
||||
"vcmi.optionsTab.simturnsMax.help" : "Spela samtidigt som andra spelare under ett angivet antal dagar eller tills en tillräckligt nära kontakt inträffar med en annan spelare",
|
||||
"vcmi.optionsTab.simturnsAI.help" : "{Simultana AI-turomgångar}\nExperimentellt alternativ. Tillåter AI-spelare att agera samtidigt som den mänskliga spelaren när simultana turomgångar är aktiverade.",
|
||||
|
||||
"vcmi.optionsTab.turnTime.select" : "Turtids-förinställningar",
|
||||
"vcmi.optionsTab.turnTime.unlimited" : "Obegränsat med tid",
|
||||
"vcmi.optionsTab.turnTime.classic.1" : "Klassisk timer: 1 minut",
|
||||
"vcmi.optionsTab.turnTime.classic.2" : "Klassisk timer: 2 minuter",
|
||||
"vcmi.optionsTab.turnTime.classic.5" : "Klassisk timer: 5 minuter",
|
||||
"vcmi.optionsTab.turnTime.classic.10" : "Klassisk timer: 10 minuter",
|
||||
"vcmi.optionsTab.turnTime.classic.20" : "Klassisk timer: 20 minuter",
|
||||
"vcmi.optionsTab.turnTime.classic.30" : "Klassisk timer: 30 minuter",
|
||||
"vcmi.optionsTab.turnTime.chess.20" : "Schack-timer: 20:00 + 10:00 + 02:00 + 00:00",
|
||||
"vcmi.optionsTab.turnTime.chess.16" : "Schack-timer: 16:00 + 08:00 + 01:30 + 00:00",
|
||||
"vcmi.optionsTab.turnTime.chess.8" : "Schack-timer: 08:00 + 04:00 + 01:00 + 00:00",
|
||||
"vcmi.optionsTab.turnTime.chess.4" : "Schack-timer: 04:00 + 02:00 + 00:30 + 00:00",
|
||||
"vcmi.optionsTab.turnTime.chess.2" : "Schack-timer: 02:00 + 01:00 + 00:15 + 00:00",
|
||||
"vcmi.optionsTab.turnTime.chess.1" : "Schack-timer: 01:00 + 01:00 + 00:00 + 00:00",
|
||||
|
||||
"vcmi.optionsTab.simturns.select" : "Välj förinställning för simultana/samtidiga turer",
|
||||
"vcmi.optionsTab.simturns.none" : "Inga simultana/samtidiga turer",
|
||||
"vcmi.optionsTab.simturns.tillContactMax" : "Simultantur: Fram till kontakt",
|
||||
"vcmi.optionsTab.simturns.tillContact1" : "Simultantur: 1 vecka, bryt vid kontakt",
|
||||
"vcmi.optionsTab.simturns.tillContact2" : "Simultantur: 2 veckor, bryt vid kontakt",
|
||||
"vcmi.optionsTab.simturns.tillContact4" : "Simultantur: 1 månad, bryt vid kontakt",
|
||||
"vcmi.optionsTab.simturns.blocked1" : "Simultantur: 1 vecka, kontakter blockerade",
|
||||
"vcmi.optionsTab.simturns.blocked2" : "Simultantur: 2 veckor, kontakter blockerade",
|
||||
"vcmi.optionsTab.simturns.blocked4" : "Simultantur: 1 månad, kontakter blockerade",
|
||||
|
||||
// Translation note: translate strings below using form that is correct for "0 days", "1 day" and "2 days" in your language
|
||||
// Using this information, VCMI will automatically select correct plural form for every possible amount
|
||||
"vcmi.optionsTab.simturns.days.0" : " %d dagar",
|
||||
"vcmi.optionsTab.simturns.days.1" : " %d dag",
|
||||
"vcmi.optionsTab.simturns.days.2" : " %d dagar",
|
||||
"vcmi.optionsTab.simturns.weeks.0" : " %d veckor",
|
||||
"vcmi.optionsTab.simturns.weeks.1" : " %d vecka",
|
||||
"vcmi.optionsTab.simturns.weeks.2" : " %d veckor",
|
||||
"vcmi.optionsTab.simturns.months.0" : " %d månader",
|
||||
"vcmi.optionsTab.simturns.months.1" : " %d månad",
|
||||
"vcmi.optionsTab.simturns.months.2" : " %d månader",
|
||||
|
||||
"vcmi.optionsTab.extraOptions.hover" : "Extra-inställningar",
|
||||
"vcmi.optionsTab.extraOptions.help" : "Ytterligare spelinställningar",
|
||||
|
||||
"vcmi.optionsTab.cheatAllowed.hover" : "Tillåter fusk i spelet",
|
||||
"vcmi.optionsTab.unlimitedReplay.hover" : "Obegränsade omspelningar av strider",
|
||||
"vcmi.optionsTab.cheatAllowed.help" : "{Tillåt fusk}\nTillåter inmatning av fuskkoder under spelets gång.",
|
||||
"vcmi.optionsTab.unlimitedReplay.help" : "{Obegränsade stridsomspelningar}\nIngen begränsning för hur många gånger man kan spela om sina strider.",
|
||||
|
||||
// Custom victory conditions for H3 campaigns and HotA maps
|
||||
"vcmi.map.victoryCondition.daysPassed.toOthers" : "Fienden har lyckats överleva fram till denna dag. Segern är deras!",
|
||||
"vcmi.map.victoryCondition.daysPassed.toSelf" : "Gratulerar! Du har lyckats överleva. Segern är er!",
|
||||
"vcmi.map.victoryCondition.eliminateMonsters.toOthers" : "Fienden har besegrat alla monster som plågade detta land och utropar seger!",
|
||||
"vcmi.map.victoryCondition.eliminateMonsters.toSelf" : "Gratulerar! Du har besegrat alla monster som plågade detta land och kan utropa seger!",
|
||||
"vcmi.map.victoryCondition.collectArtifacts.message" : "Förvärva tre artefakter",
|
||||
"vcmi.map.victoryCondition.angelicAlliance.toSelf" : "Gratulerar! Alla dina fiender har besegrats och du har 'Änglaalliansen'! Segern är din!",
|
||||
"vcmi.map.victoryCondition.angelicAlliance.message" : "Besegra alla fiender och sätt ihop 'Änglaalliansen'",
|
||||
"vcmi.map.victoryCondition.angelicAlliancePartLost.toSelf" : "Ack, du har förlorat en del av 'Änglaalliansen'. Allt är förlorat.",
|
||||
|
||||
// few strings from WoG used by vcmi
|
||||
"vcmi.stackExperience.description" : "» V a r e l s e f ö r b a n d s d e t a l j e r «\n\nVarelsetyp ................... : %s\nErfarenhetsrank ................. : %s (%i)\nErfarenhetspoäng ............... : %i\nErfarenhetspoäng till nästa rank .. : %i\nMaximal erfarenhet per strid ... : %i%% (%i)\nAntal varelser i förbandet .... : %i\nMaximalt antal nya rekryter\n utan att förlora nuvarande rank .... : %i\nErfarenhetsmultiplikator ........... : %.2f\nUppgradera erfarenhetsmultiplikator .............. : %.2f\nErfarenhet efter rank 10 ........ : %i\nMaximalt antal nyrekryteringar för att stanna kvar på\n rank 10 vid maximal erfarenhet : %i",
|
||||
"vcmi.stackExperience.rank.0" : "Grundläggande",
|
||||
"vcmi.stackExperience.rank.1" : "Novis",
|
||||
"vcmi.stackExperience.rank.2" : "Tränad",
|
||||
"vcmi.stackExperience.rank.3" : "Erfaren",
|
||||
"vcmi.stackExperience.rank.4" : "Beprövad",
|
||||
"vcmi.stackExperience.rank.5" : "Veteran",
|
||||
"vcmi.stackExperience.rank.6" : "Adept",
|
||||
"vcmi.stackExperience.rank.7" : "Expert",
|
||||
"vcmi.stackExperience.rank.8" : "Elit",
|
||||
"vcmi.stackExperience.rank.9" : "Mästare",
|
||||
"vcmi.stackExperience.rank.10" : "Äss",
|
||||
|
||||
// Strings for HotA Seer Hut / Quest Guards
|
||||
"core.seerhut.quest.heroClass.complete.0" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?",
|
||||
"core.seerhut.quest.heroClass.complete.1" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?",
|
||||
"core.seerhut.quest.heroClass.complete.2" : "Ah, du är %s. Här är en gåva till dig. Tar du emot den?",
|
||||
"core.seerhut.quest.heroClass.complete.3" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?",
|
||||
"core.seerhut.quest.heroClass.complete.4" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?",
|
||||
"core.seerhut.quest.heroClass.complete.5" : "Vakterna ser att du är %s och erbjuder att låta dig passera. Accepterar du?",
|
||||
"core.seerhut.quest.heroClass.description.0" : "Skicka %s till %s",
|
||||
"core.seerhut.quest.heroClass.description.1" : "Skicka %s till %s",
|
||||
"core.seerhut.quest.heroClass.description.2" : "Skicka %s till %s",
|
||||
"core.seerhut.quest.heroClass.description.3" : "Skicka %s för att öppna grinden",
|
||||
"core.seerhut.quest.heroClass.description.4" : "Skicka %s till öppen grind",
|
||||
"core.seerhut.quest.heroClass.description.5" : "Skicka %s till öppen grind",
|
||||
"core.seerhut.quest.heroClass.hover.0" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.hover.1" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.hover.2" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.hover.3" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.hover.4" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.hover.5" : "(söker hjälte i %s klass)",
|
||||
"core.seerhut.quest.heroClass.receive.0" : "Jag har en gåva till %s.",
|
||||
"core.seerhut.quest.heroClass.receive.1" : "Jag har en gåva till %s.",
|
||||
"core.seerhut.quest.heroClass.receive.2" : "Jag har en gåva till %s.",
|
||||
"core.seerhut.quest.heroClass.receive.3" : "Vakterna här säger att de bara kommer att låta %s passera.",
|
||||
"core.seerhut.quest.heroClass.receive.4" : "Vakterna här säger att de bara kommer att låta %s passera.",
|
||||
"core.seerhut.quest.heroClass.receive.5" : "Vakterna här säger att de bara kommer att låta %s passera.",
|
||||
"core.seerhut.quest.heroClass.visit.0" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!",
|
||||
"core.seerhut.quest.heroClass.visit.1" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!",
|
||||
"core.seerhut.quest.heroClass.visit.2" : "Du är inte %s. Det finns inget här för dig att hämta. Försvinn!",
|
||||
"core.seerhut.quest.heroClass.visit.3" : "Vakterna här kommer bara att låta %s passera.",
|
||||
"core.seerhut.quest.heroClass.visit.4" : "Vakterna här kommer bara att låta %s passera.",
|
||||
"core.seerhut.quest.heroClass.visit.5" : "Vakterna här kommer bara att låta %s passera.",
|
||||
|
||||
"core.seerhut.quest.reachDate.complete.0" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?",
|
||||
"core.seerhut.quest.reachDate.complete.1" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?",
|
||||
"core.seerhut.quest.reachDate.complete.2" : "Jag är fri nu. Det här är vad jag har att erbjuda dig. Accepterar du?",
|
||||
"core.seerhut.quest.reachDate.complete.3" : "Du är fri att gå igenom nu. Önskar ni att passera?",
|
||||
"core.seerhut.quest.reachDate.complete.4" : "Du är fri att gå igenom nu. Önskar ni att passera?",
|
||||
"core.seerhut.quest.reachDate.complete.5" : "Du är fri att gå igenom nu. Önskar ni att passera?",
|
||||
"core.seerhut.quest.reachDate.description.0" : "Vänta tills %s för %s",
|
||||
"core.seerhut.quest.reachDate.description.1" : "Vänta tills %s för %s",
|
||||
"core.seerhut.quest.reachDate.description.2" : "Vänta tills %s för %s",
|
||||
"core.seerhut.quest.reachDate.description.3" : "Vänta tills %s öppnar grinden",
|
||||
"core.seerhut.quest.reachDate.description.4" : "Vänta tills %s öppnar grinden",
|
||||
"core.seerhut.quest.reachDate.description.5" : "Vänta tills %s öppnar grinden",
|
||||
"core.seerhut.quest.reachDate.hover.0" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.hover.1" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.hover.2" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.hover.3" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.hover.4" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.hover.5" : "(Återvänd inte före %s)",
|
||||
"core.seerhut.quest.reachDate.receive.0" : "Jag är upptagen. Kom inte tillbaka före %s",
|
||||
"core.seerhut.quest.reachDate.receive.1" : "Jag är upptagen. Kom inte tillbaka före %s",
|
||||
"core.seerhut.quest.reachDate.receive.2" : "Jag är upptagen. Kom inte tillbaka före %s",
|
||||
"core.seerhut.quest.reachDate.receive.3" : "Stängt fram till %s.",
|
||||
"core.seerhut.quest.reachDate.receive.4" : "Stängt fram till %s.",
|
||||
"core.seerhut.quest.reachDate.receive.5" : "Stängt fram till %s.",
|
||||
"core.seerhut.quest.reachDate.visit.0" : "Jag är upptagen. Kom inte tillbaka före %s.",
|
||||
"core.seerhut.quest.reachDate.visit.1" : "Jag är upptagen. Kom inte tillbaka före %s.",
|
||||
"core.seerhut.quest.reachDate.visit.2" : "Jag är upptagen. Kom inte tillbaka före %s.",
|
||||
"core.seerhut.quest.reachDate.visit.3" : "Stängt fram till %s.",
|
||||
"core.seerhut.quest.reachDate.visit.4" : "Stängt fram till %s.",
|
||||
"core.seerhut.quest.reachDate.visit.5" : "Stängt fram till %s.",
|
||||
|
||||
"core.bonus.ADDITIONAL_ATTACK.name" : "Dubbelslag",
|
||||
"core.bonus.ADDITIONAL_ATTACK.description" : "Attackerar två gånger",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.name" : "Ytterligare motattacker",
|
||||
"core.bonus.ADDITIONAL_RETALIATION.description" : "Kan slå tillbaka ${val} extra gånger",
|
||||
"core.bonus.AIR_IMMUNITY.name" : "Luft-immunitet",
|
||||
"core.bonus.AIR_IMMUNITY.description" : "Immun mot alla trollformler från skolan för luftmagi",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.name" : "Attackera runtomkring",
|
||||
"core.bonus.ATTACKS_ALL_ADJACENT.description" : "Attackerar alla angränsande fiender",
|
||||
"core.bonus.BLOCKS_RETALIATION.name" : "Ingen motattack",
|
||||
"core.bonus.BLOCKS_RETALIATION.description" : "Fienden kan inte slå tillbaka/retaliera",
|
||||
"core.bonus.BLOCKS_RANGED_RETALIATION.name" : "Ingen motattack på avstånd",
|
||||
"core.bonus.BLOCKS_RANGED_RETALIATION.description" : "Fienden kan inte göra en motattack/retaliering på avstånd genom att använda en distansattack",
|
||||
"core.bonus.CATAPULT.name" : "Katapult",
|
||||
"core.bonus.CATAPULT.description" : "Attackerar belägringsmurar",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.name" : "Minska trollformelkostnaden (${val})",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ALLY.description" : "Minskar trollformelkostnaden för hjälten med ${val}",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.name" : "Magisk dämpare (${val})",
|
||||
"core.bonus.CHANGES_SPELL_COST_FOR_ENEMY.description": "Ökar trollformelkostnaden för fiendens trollformler med ${val}",
|
||||
"core.bonus.CHARGE_IMMUNITY.name" : "Galoppanfalls-immunitet",
|
||||
"core.bonus.CHARGE_IMMUNITY.description" : "Immun mot ryttares och tornerares galopperande ridanfall",
|
||||
"core.bonus.DARKNESS.name" : "I skydd av mörkret",
|
||||
"core.bonus.DARKNESS.description" : "Skapar ett hölje av mörker med en ${val}-rutorsradie",
|
||||
"core.bonus.DEATH_STARE.name" : "Dödsblick (${val}%)",
|
||||
"core.bonus.DEATH_STARE.description" : "Varje förbandsenhet med 'Dödsblick' har ${val}% chans att döda den översta enheten i ett fiendeförband",
|
||||
"core.bonus.DEFENSIVE_STANCE.name" : "Försvarshållning",
|
||||
"core.bonus.DEFENSIVE_STANCE.description" : "Ger ytterligare +${val} till enhetens försvarsförmåga när du väljer att försvarar dig",
|
||||
"core.bonus.DESTRUCTION.name" : "Förintelse",
|
||||
"core.bonus.DESTRUCTION.description" : "Har ${val}% chans att döda extra enheter efter attack",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.name" : "Dödsstöt",
|
||||
"core.bonus.DOUBLE_DAMAGE_CHANCE.description" : "Har ${val}% chans att ge dubbel basskada vid attack",
|
||||
"core.bonus.DRAGON_NATURE.name" : "Drake",
|
||||
"core.bonus.DRAGON_NATURE.description" : "Varelsen har en draknatur",
|
||||
"core.bonus.EARTH_IMMUNITY.name" : "Jord-immunitet",
|
||||
"core.bonus.EARTH_IMMUNITY.description" : "Immun mot alla trollformler från skolan för jordmagi",
|
||||
"core.bonus.ENCHANTER.name" : "Förtrollare",
|
||||
"core.bonus.ENCHANTER.description" : "Kan kasta ${subtyp.spell} på alla varje tur/omgång",
|
||||
"core.bonus.ENCHANTED.name" : "Förtrollad",
|
||||
"core.bonus.ENCHANTED.description" : "Påverkas av permanent ${subtype.spell}",
|
||||
"core.bonus.ENEMY_ATTACK_REDUCTION.name" : "Avfärda attack (${val}%)",
|
||||
"core.bonus.ENEMY_ATTACK_REDUCTION.description" : "När du blir attackerad ignoreras ${val}% av angriparens attack",
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.name" : "Förbigå försvar (${val}%)",
|
||||
"core.bonus.ENEMY_DEFENCE_REDUCTION.description" : "När du attackerar ignoreras ${val}% av försvararens försvar",
|
||||
"core.bonus.FIRE_IMMUNITY.name" : "Eld-immunitet",
|
||||
"core.bonus.FIRE_IMMUNITY.description" : "Immun mot alla trollformler från skolan för eldmagi",
|
||||
"core.bonus.FIRE_SHIELD.name" : "Eldsköld (${val}%)",
|
||||
"core.bonus.FIRE_SHIELD.description" : "Reflekterar en del av närstridsskadorna",
|
||||
"core.bonus.FIRST_STRIKE.name" : "Första slaget",
|
||||
"core.bonus.FIRST_STRIKE.description" : "Denna varelse gör en motattack innan den blir attackerad",
|
||||
"core.bonus.FEAR.name" : "Rädsla",
|
||||
"core.bonus.FEAR.description" : "Orsakar rädsla på ett fiendeförband",
|
||||
"core.bonus.FEARLESS.name" : "Orädd",
|
||||
"core.bonus.FEARLESS.description" : "Immun mot rädsla",
|
||||
"core.bonus.FEROCITY.name" : "Vildsint",
|
||||
"core.bonus.FEROCITY.description" : "Attackerar ${val} extra gång(er) om någon dödas",
|
||||
"core.bonus.FLYING.name" : "Flygande",
|
||||
"core.bonus.FLYING.description" : "Flyger vid förflyttning (ignorerar hinder)",
|
||||
"core.bonus.FREE_SHOOTING.name" : "Skjut på nära håll",
|
||||
"core.bonus.FREE_SHOOTING.description" : "Kan använda distansattacker på närstridsavstånd",
|
||||
"core.bonus.GARGOYLE.name" : "Stenfigur",
|
||||
"core.bonus.GARGOYLE.description" : "Kan varken upplivas eller läkas",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.name" : "Minska skada (${val}%)",
|
||||
"core.bonus.GENERAL_DAMAGE_REDUCTION.description" : "Reducerar fysisk skada från både distans- och närstridsattacker",
|
||||
"core.bonus.HATE.name" : "Hatar ${subtyp.varelse}",
|
||||
"core.bonus.HATE.description" : "Gör ${val}% mer skada mot ${subtyp.varelse}",
|
||||
"core.bonus.HEALER.name" : "Helare",
|
||||
"core.bonus.HEALER.description" : "Helar/läker allierade enheter",
|
||||
"core.bonus.HP_REGENERATION.name" : "Självläkande",
|
||||
"core.bonus.HP_REGENERATION.description" : "Får tillbaka ${val} träffpoäng (hälsa) varje runda",
|
||||
"core.bonus.JOUSTING.name" : "Galopperande ridanfall",
|
||||
"core.bonus.JOUSTING.description" : "Orsakar +${val}% extra skada för varje ruta som enheten förflyttas innan attack",
|
||||
"core.bonus.KING.name" : "Kung",
|
||||
"core.bonus.KING.description" : "Sårbar för 'Dräpar'-nivå ${val} eller högre",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.name" : "Förtrollningsimmunitet 1-${val}",
|
||||
"core.bonus.LEVEL_SPELL_IMMUNITY.description" : "Immun mot trollformler på nivå 1-${val}",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.name" : "Begränsad räckvidd för skjutning",
|
||||
"core.bonus.LIMITED_SHOOTING_RANGE.description" : "Kan inte sikta på enheter längre bort än ${val} rutor",
|
||||
"core.bonus.LIFE_DRAIN.name" : "Dränerar livskraft (${val}%)",
|
||||
"core.bonus.LIFE_DRAIN.description" : "Dränerar ${val}% träffpoäng (hälsa) av utdelad skada",
|
||||
"core.bonus.MANA_CHANNELING.name" : "Kanalisera trollformelspoäng ${val}%",
|
||||
"core.bonus.MANA_CHANNELING.description" : "Ger din hjälte ${val}% av den mängd trollformelspoäng som fienden spenderar per trollformel i strid",
|
||||
"core.bonus.MANA_DRAIN.name" : "Dränera trollformelspoäng",
|
||||
"core.bonus.MANA_DRAIN.description" : "Dränerar ${val} trollformelspoäng varje tur",
|
||||
"core.bonus.MAGIC_MIRROR.name" : "Magisk spegel (${val}%)",
|
||||
"core.bonus.MAGIC_MIRROR.description" : "Har ${val}% chans att reflektera (omdirigera) en offensiv trollformel på en fiendeenhet",
|
||||
"core.bonus.MAGIC_RESISTANCE.name" : "Magiskt motstånd (${val}%)",
|
||||
"core.bonus.MAGIC_RESISTANCE.description" : "Har en ${val}% chans att motstå en skadlig trollformel",
|
||||
"core.bonus.MIND_IMMUNITY.name" : "Immunitet mot sinnesförtrollningar",
|
||||
"core.bonus.MIND_IMMUNITY.description" : "Immun mot förtrollningar som påverkar dina sinnen",
|
||||
"core.bonus.NO_DISTANCE_PENALTY.name" : "Ingen avståndsbestraffning",
|
||||
"core.bonus.NO_DISTANCE_PENALTY.description" : "Gör full skada på vilket avstånd som helst i strid",
|
||||
"core.bonus.NO_MELEE_PENALTY.name" : "Ingen närstridsbestraffning",
|
||||
"core.bonus.NO_MELEE_PENALTY.description" : "Varelsen har ingen närstridsbestraffning",
|
||||
"core.bonus.NO_MORALE.name" : "Ingen Moralpåverkan",
|
||||
"core.bonus.NO_MORALE.description" : "Varelsen är immun mot moraliska effekter och har alltid neutral moral",
|
||||
"core.bonus.NO_WALL_PENALTY.name" : "Ingen murbestraffning",
|
||||
"core.bonus.NO_WALL_PENALTY.description" : "Orsakar full skada mot fiender bakom en mur",
|
||||
"core.bonus.NON_LIVING.name" : "Icke levande",
|
||||
"core.bonus.NON_LIVING.description" : "Immunitet mot många effekter som annars bara påverkar levande och odöda varelser",
|
||||
"core.bonus.RANDOM_SPELLCASTER.name" : "Slumpmässig besvärjare",
|
||||
"core.bonus.RANDOM_SPELLCASTER.description" : "Kan kasta trollformler som väljs slumpmässigt",
|
||||
"core.bonus.RANGED_RETALIATION.name" : "Motattacker på avstånd",
|
||||
"core.bonus.RANGED_RETALIATION.description" : "Kan retaliera/motattackera på avstånd",
|
||||
"core.bonus.RECEPTIVE.name" : "Mottaglig",
|
||||
"core.bonus.RECEPTIVE.description" : "Ingen immunitet mot vänliga besvärjelser",
|
||||
"core.bonus.REBIRTH.name" : "Återfödelse (${val}%)",
|
||||
"core.bonus.REBIRTH.description" : "${val}% av enheterna kommer att återuppväckas efter döden",
|
||||
"core.bonus.RETURN_AFTER_STRIKE.name" : "Återvänder efter närstrid",
|
||||
"core.bonus.RETURN_AFTER_STRIKE.description" : "Efter att ha attackerat en fiendeenhet i närstrid återvänder enheten till rutan som den var placerad på innan den utförde sin närstridsattack",
|
||||
"core.bonus.REVENGE.name" : "Hämnd",
|
||||
"core.bonus.REVENGE.description" : "Orsakar extra skada baserat på angriparens förlorade träffpoäng (hälsa) i strid",
|
||||
"core.bonus.SHOOTER.name" : "Distans-attack",
|
||||
"core.bonus.SHOOTER.description" : "Varelsen kan skjuta/attackera på avstånd",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.name" : "Skjuter alla i närheten",
|
||||
"core.bonus.SHOOTS_ALL_ADJACENT.description" : "Denna varelses distans-attacker drabbar alla mål i ett litet område",
|
||||
"core.bonus.SOUL_STEAL.name" : "Själtjuv",
|
||||
"core.bonus.SOUL_STEAL.description" : "Återuppväcker ${val} av sina egna enheter för varje dödad fiendeenhet",
|
||||
"core.bonus.SPELLCASTER.name" : "Besvärjare",
|
||||
"core.bonus.SPELLCASTER.description" : "Kan kasta ${subtype.spell}",
|
||||
"core.bonus.SPELL_AFTER_ATTACK.name" : "Besvärja efter attack",
|
||||
"core.bonus.SPELL_AFTER_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} efter att den har attackerat",
|
||||
"core.bonus.SPELL_BEFORE_ATTACK.name" : "Besvärja före attack",
|
||||
"core.bonus.SPELL_BEFORE_ATTACK.description" : "Har en ${val}% chans att kasta ${subtype.spell} innan den attackerar",
|
||||
"core.bonus.SPELL_DAMAGE_REDUCTION.name" : "Trolldoms-resistens",
|
||||
"core.bonus.SPELL_DAMAGE_REDUCTION.description" : "Skadan från trollformler är reducet med ${val}%.",
|
||||
"core.bonus.SPELL_IMMUNITY.name" : "Trolldoms-immunitet",
|
||||
"core.bonus.SPELL_IMMUNITY.description" : "Immun mot ${subtype.spell}",
|
||||
"core.bonus.SPELL_LIKE_ATTACK.name" : "Trolldomsliknande attack",
|
||||
"core.bonus.SPELL_LIKE_ATTACK.description" : "Attackerar med ${subtype.spell}",
|
||||
"core.bonus.SPELL_RESISTANCE_AURA.name" : "Motståndsaura",
|
||||
"core.bonus.SPELL_RESISTANCE_AURA.description" : "Närbelägna förband får ${val}% magi-resistens",
|
||||
"core.bonus.SUMMON_GUARDIANS.name" : "Åkalla väktare",
|
||||
"core.bonus.SUMMON_GUARDIANS.description" : "I början av striden åkallas ${subtype.creature} (${val}%)",
|
||||
"core.bonus.SYNERGY_TARGET.name" : "Synergibar",
|
||||
"core.bonus.SYNERGY_TARGET.description" : "Denna varelse är sårbar för synergieffekt",
|
||||
"core.bonus.TWO_HEX_ATTACK_BREATH.name" : "Dödlig andedräkt",
|
||||
"core.bonus.TWO_HEX_ATTACK_BREATH.description" : "Andningsattack (2 rutors räckvidd)",
|
||||
"core.bonus.THREE_HEADED_ATTACK.name" : "Trehövdad attack",
|
||||
"core.bonus.THREE_HEADED_ATTACK.description" : "Attackerar tre angränsande enheter",
|
||||
"core.bonus.TRANSMUTATION.name" : "Transmutation",
|
||||
"core.bonus.TRANSMUTATION.description" : "${val}% chans att förvandla angripen enhet till en annan typ",
|
||||
"core.bonus.UNDEAD.name" : "Odöd",
|
||||
"core.bonus.UNDEAD.description" : "Varelsen är odöd",
|
||||
"core.bonus.UNLIMITED_RETALIATIONS.name" : "Obegränsat antal motattacker",
|
||||
"core.bonus.UNLIMITED_RETALIATIONS.description" : "Kan slå tillbaka mot ett obegränsat antal attacker varje omgång",
|
||||
"core.bonus.WATER_IMMUNITY.name" : "Vatten-immunitet",
|
||||
"core.bonus.WATER_IMMUNITY.description" : "Immun mot alla trollformler från vattenmagi-skolan",
|
||||
"core.bonus.WIDE_BREATH.name" : "Bred dödlig andedräkt",
|
||||
"core.bonus.WIDE_BREATH.description" : "Bred andningsattack (flera rutor)",
|
||||
"core.bonus.DISINTEGRATE.name" : "Desintegrerar",
|
||||
"core.bonus.DISINTEGRATE.description" : "Ingen fysisk kropp finns kvar efter att enheten blivit besegrad i strid",
|
||||
"core.bonus.INVINCIBLE.name" : "Oövervinnerlig",
|
||||
"core.bonus.INVINCIBLE.description" : "Kan inte påverkas av någonting"
|
||||
}
|
@ -77,17 +77,6 @@
|
||||
]
|
||||
},
|
||||
|
||||
"ukrainian" : {
|
||||
"name" : "VCMI - ключові файли",
|
||||
"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
|
||||
"author" : "Команда VCMI",
|
||||
|
||||
"skipValidation" : true,
|
||||
"translations" : [
|
||||
"config/vcmi/ukrainian.json"
|
||||
]
|
||||
},
|
||||
|
||||
"spanish" : {
|
||||
"name" : "VCMI - ficheros necesarios",
|
||||
"description" : "Ficheros necesarios para ejecutar VCMI correctamente",
|
||||
@ -99,6 +88,28 @@
|
||||
]
|
||||
},
|
||||
|
||||
"swedish" : {
|
||||
"name" : "Nödvändiga VCMI-filer",
|
||||
"description" : "Filer som behövs för att köra VCMI korrekt",
|
||||
"author" : "Maurycy (XCOM-HUB on GitHub)",
|
||||
|
||||
"skipValidation" : true,
|
||||
"translations" : [
|
||||
"config/vcmi/swedish.json"
|
||||
]
|
||||
},
|
||||
|
||||
"ukrainian" : {
|
||||
"name" : "VCMI - ключові файли",
|
||||
"description" : "Ключові файли необхідні для повноцінної роботи VCMI",
|
||||
"author" : "Команда VCMI",
|
||||
|
||||
"skipValidation" : true,
|
||||
"translations" : [
|
||||
"config/vcmi/ukrainian.json"
|
||||
]
|
||||
},
|
||||
|
||||
"vietnamese": {
|
||||
"name": "VCMI essential files",
|
||||
"description": "Các tập tin cần thiết để chạy VCMI",
|
||||
|
@ -420,6 +420,8 @@ void CPlayerInterface::heroCreated(const CGHeroInstance * hero)
|
||||
EVENT_HANDLER_CALLED_BY_CLIENT;
|
||||
localState->addWanderingHero(hero);
|
||||
adventureInt->onHeroChanged(hero);
|
||||
if(castleInt)
|
||||
CCS->soundh->playSound(soundBase::newBuilding);
|
||||
}
|
||||
void CPlayerInterface::openTownWindow(const CGTownInstance * town)
|
||||
{
|
||||
|
@ -188,7 +188,7 @@ public:
|
||||
bool swapGarrisonOnSiege(ObjectInstanceID tid) override {return false;};
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override {return false;};
|
||||
bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override {return false;};
|
||||
bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble) override {return false;};
|
||||
bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional<bool> askAssemble) override {return false;};
|
||||
void removeArtifact(const ArtifactLocation & al) override {};
|
||||
bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;};
|
||||
|
||||
|
@ -743,7 +743,7 @@ void BattleWindow::bSpellf()
|
||||
const auto artID = blockingBonus->sid.as<ArtifactID>();
|
||||
//If we have artifact, put name of our hero. Otherwise assume it's the enemy.
|
||||
//TODO check who *really* is source of bonus
|
||||
std::string heroName = myHero->hasArt(artID) ? myHero->getNameTranslated() : owner.enemyHero().name;
|
||||
std::string heroName = myHero->hasArt(artID, true) ? myHero->getNameTranslated() : owner.enemyHero().name;
|
||||
|
||||
//%s wields the %s, an ancient artifact which creates a p dead to all magic.
|
||||
LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[683])
|
||||
|
@ -1037,11 +1037,11 @@ OptionsTab::PlayerOptionsEntry::PlayerOptionsEntry(const PlayerSettings & S, con
|
||||
}
|
||||
const auto & font = GH.renderHandler().loadFont(FONT_SMALL);
|
||||
|
||||
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
|
||||
labelWhoCanPlay = std::make_shared<CMultiLineLabel>(Rect(6, 23, 45, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, CGI->generaltexth->arraytxt[206 + whoCanPlay]);
|
||||
|
||||
auto hasHandicap = [this](){ return s->handicap.startBonus.empty() && s->handicap.percentIncome == 100 && s->handicap.percentGrowth == 100; };
|
||||
std::string labelHandicapText = hasHandicap() ? CGI->generaltexth->arraytxt[210] : MetaString::createFromTextID("vcmi.lobby.handicap").toString();
|
||||
labelHandicap = std::make_shared<CMultiLineLabel>(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::CENTER, Colors::WHITE, labelHandicapText);
|
||||
labelHandicap = std::make_shared<CMultiLineLabel>(Rect(57, 24, 47, font->getLineHeight()*2), EFonts::FONT_TINY, ETextAlignment::TOPCENTER, Colors::WHITE, labelHandicapText);
|
||||
handicap = std::make_shared<LRClickableArea>(Rect(56, 24, 49, font->getLineHeight()*2), [](){
|
||||
if(!CSH->isHost())
|
||||
return;
|
||||
|
@ -212,6 +212,12 @@ CBitmapFont::CBitmapFont(const std::string & filename):
|
||||
SDL_FreeSurface(atlasImage);
|
||||
atlasImage = scaledSurface;
|
||||
}
|
||||
|
||||
logGlobal->debug("Loaded BMP font: '%s', height %d, ascent %d",
|
||||
filename,
|
||||
getLineHeightScaled(),
|
||||
getFontAscentScaled()
|
||||
);
|
||||
}
|
||||
|
||||
CBitmapFont::~CBitmapFont()
|
||||
|
@ -73,6 +73,14 @@ CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
|
||||
TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
|
||||
TTF_SetFontHinting(font.get(),TTF_HINTING_MONO);
|
||||
|
||||
logGlobal->debug("Loaded TTF font: '%s', point size %d, height %d, ascent %d, descent %d, line skip %d",
|
||||
fontConfig["file"].String(),
|
||||
getPointSize(fontConfig["size"]),
|
||||
TTF_FontHeight(font.get()),
|
||||
TTF_FontAscent(font.get()),
|
||||
TTF_FontDescent(font.get()),
|
||||
TTF_FontLineSkip(font.get())
|
||||
);
|
||||
}
|
||||
|
||||
CTrueTypeFont::~CTrueTypeFont() = default;
|
||||
|
@ -13,8 +13,13 @@
|
||||
#include "CTrueTypeFont.h"
|
||||
#include "CBitmapFont.h"
|
||||
|
||||
#include "../CGameInfo.h"
|
||||
|
||||
#include "../../lib/CConfigHandler.h"
|
||||
#include "../../lib/modding/CModHandler.h"
|
||||
#include "../../lib/texts/TextOperations.h"
|
||||
#include "../../lib/texts/CGeneralTextHandler.h"
|
||||
#include "../../lib/texts/Languages.h"
|
||||
|
||||
void FontChain::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const
|
||||
{
|
||||
@ -39,7 +44,7 @@ size_t FontChain::getFontAscentScaled() const
|
||||
return maxHeight;
|
||||
}
|
||||
|
||||
bool FontChain::bitmapFontsPrioritized() const
|
||||
bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const
|
||||
{
|
||||
const std::string & fontType = settings["video"]["fontsType"].String();
|
||||
if (fontType == "original")
|
||||
@ -55,6 +60,19 @@ bool FontChain::bitmapFontsPrioritized() const
|
||||
if (!vstd::isAlmostEqual(1.0, settings["video"]["fontScalingFactor"].Float()))
|
||||
return false; // If player requested non-100% scaling - use scalable fonts
|
||||
|
||||
std::string modName = CGI->modh->findResourceOrigin(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT));
|
||||
std::string fontLanguage = CGI->modh->getModLanguage(modName);
|
||||
std::string gameLanguage = CGI->generaltexth->getPreferredLanguage();
|
||||
std::string fontEncoding = Languages::getLanguageOptions(fontLanguage).encoding;
|
||||
std::string gameEncoding = Languages::getLanguageOptions(gameLanguage).encoding;
|
||||
|
||||
// player uses language with different encoding than his bitmap fonts
|
||||
// for example, Polish language with English fonts or Chinese language which can't use H3 fonts at all
|
||||
// this may result in unintended mixing of ttf and bitmap fonts, which may have a bit different look
|
||||
// so in this case prefer ttf fonts that are likely to cover target language better than H3 fonts
|
||||
if (fontEncoding != gameEncoding)
|
||||
return false;
|
||||
|
||||
return true; // else - use original bitmap fonts
|
||||
}
|
||||
|
||||
@ -65,7 +83,7 @@ void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig)
|
||||
|
||||
void FontChain::addBitmapFont(const std::string & bitmapFilename)
|
||||
{
|
||||
if (bitmapFontsPrioritized())
|
||||
if (bitmapFontsPrioritized(bitmapFilename))
|
||||
chain.insert(chain.begin(), std::make_unique<CBitmapFont>(bitmapFilename));
|
||||
else
|
||||
chain.push_back(std::make_unique<CBitmapFont>(bitmapFilename));
|
||||
|
@ -29,7 +29,7 @@ class FontChain final : public IFont
|
||||
|
||||
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
|
||||
size_t getFontAscentScaled() const override;
|
||||
bool bitmapFontsPrioritized() const;
|
||||
bool bitmapFontsPrioritized(const std::string & bitmapFontName) const;
|
||||
public:
|
||||
FontChain() = default;
|
||||
|
||||
|
@ -342,6 +342,8 @@ std::shared_ptr<const IFont> RenderHandler::loadFont(EFonts font)
|
||||
return fonts.at(font);
|
||||
|
||||
const int8_t index = static_cast<int8_t>(font);
|
||||
logGlobal->debug("Loading font %d", static_cast<int>(index));
|
||||
|
||||
auto configList = CResourceHandler::get()->getResourcesWithName(JsonPath::builtin("config/fonts.json"));
|
||||
std::shared_ptr<FontChain> loadedFont = std::make_shared<FontChain>();
|
||||
std::string bitmapPath;
|
||||
|
@ -137,9 +137,9 @@ void CArtifactsOfHeroBase::scrollBackpack(bool left)
|
||||
LOCPLINT->cb->scrollBackpackArtifacts(curHero->id, left);
|
||||
}
|
||||
|
||||
void CArtifactsOfHeroBase::markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved)
|
||||
void CArtifactsOfHeroBase::markPossibleSlots(const CArtifact * art, bool assumeDestRemoved)
|
||||
{
|
||||
for(auto artPlace : artWorn)
|
||||
for(const auto & artPlace : artWorn)
|
||||
artPlace.second->selectSlot(art->canBePutAt(curHero, artPlace.second->slot, assumeDestRemoved));
|
||||
}
|
||||
|
||||
@ -271,7 +271,7 @@ void CArtifactsOfHeroBase::setSlotData(ArtPlacePtr artPlace, const ArtifactPosit
|
||||
arts.try_emplace(combinedArt->getId(), std::vector<ArtifactID>{});
|
||||
for(const auto part : combinedArt->getConstituents())
|
||||
{
|
||||
if(curHero->hasArt(part->getId(), false, false, false))
|
||||
if(curHero->hasArt(part->getId(), false, false))
|
||||
arts.at(combinedArt->getId()).emplace_back(part->getId());
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public:
|
||||
virtual void setHero(const CGHeroInstance * hero);
|
||||
virtual const CGHeroInstance * getHero() const;
|
||||
virtual void scrollBackpack(bool left);
|
||||
virtual void markPossibleSlots(const CArtifactInstance * art, bool assumeDestRemoved = true);
|
||||
virtual void markPossibleSlots(const CArtifact * art, bool assumeDestRemoved = true);
|
||||
virtual void unmarkSlots();
|
||||
virtual ArtPlacePtr getArtPlace(const ArtifactPosition & slot);
|
||||
virtual ArtPlacePtr getArtPlace(const Point & cursorPosition);
|
||||
|
@ -62,15 +62,12 @@ const CGHeroInstance * CWindowWithArtifacts::getHeroPickedArtifact() const
|
||||
|
||||
const CArtifactInstance * CWindowWithArtifacts::getPickedArtifact() const
|
||||
{
|
||||
const CArtifactInstance * art = nullptr;
|
||||
|
||||
for(const auto & artSet : artSets)
|
||||
if(const auto pickedArt = artSet->getHero()->getArt(ArtifactPosition::TRANSITION_POS))
|
||||
{
|
||||
art = pickedArt;
|
||||
break;
|
||||
return pickedArt;
|
||||
}
|
||||
return art;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CWindowWithArtifacts::clickPressedOnArtPlace(const CGHeroInstance * hero, const ArtifactPosition & slot,
|
||||
@ -207,7 +204,7 @@ void CWindowWithArtifacts::markPossibleSlots() const
|
||||
continue;
|
||||
|
||||
if(getHeroPickedArtifact() == hero || !std::dynamic_pointer_cast<CArtifactsOfHeroKingdom>(artSet))
|
||||
artSet->markPossibleSlots(pickedArtInst, hero->tempOwner == LOCPLINT->playerID);
|
||||
artSet->markPossibleSlots(pickedArtInst->artType, hero->tempOwner == LOCPLINT->playerID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,10 +55,6 @@
|
||||
"upgrades": ["stormElemental"],
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CAELEM.DEF"
|
||||
},
|
||||
"sound" :
|
||||
@ -122,10 +118,6 @@
|
||||
"upgrades": ["magmaElemental"],
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CEELEM.DEF"
|
||||
},
|
||||
"sound" :
|
||||
@ -185,10 +177,6 @@
|
||||
"upgrades": ["energyElemental"],
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CFELEM.DEF"
|
||||
},
|
||||
"sound" :
|
||||
@ -273,10 +261,6 @@
|
||||
"upgrades": ["iceElemental"],
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CWELEM.DEF"
|
||||
},
|
||||
"sound" :
|
||||
@ -472,10 +456,6 @@
|
||||
"graphics" :
|
||||
{
|
||||
"animation": "CICEE.DEF",
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"missile" :
|
||||
{
|
||||
"projectile": "PICEE.DEF"
|
||||
@ -554,10 +534,6 @@
|
||||
},
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CSTONE.DEF"
|
||||
},
|
||||
"sound" :
|
||||
@ -635,10 +611,6 @@
|
||||
"graphics" :
|
||||
{
|
||||
"animation": "CSTORM.DEF",
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"missile" :
|
||||
{
|
||||
"projectile": "CPRGTIX.DEF"
|
||||
@ -717,10 +689,6 @@
|
||||
},
|
||||
"graphics" :
|
||||
{
|
||||
"animationTime" :
|
||||
{
|
||||
"idle" : 0
|
||||
},
|
||||
"animation": "CNRG.DEF"
|
||||
},
|
||||
"sound" :
|
||||
|
@ -29,14 +29,14 @@
|
||||
// "noShadow" - if set, this font will not drop any shadow
|
||||
"trueType":
|
||||
{
|
||||
"BIGFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 19, 39, 58, 78] },
|
||||
"BIGFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 18, 38, 57, 76] },
|
||||
"CALLI10R" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 12, 24, 36, 48] }, // TODO: find better matching font? This is likely non-free 'Callisto MT' font
|
||||
"CREDITS" : { "file" : "NotoSerif-Black.ttf", "size" : [ 22, 44, 66, 88], "outline" : true },
|
||||
"HISCORE" : { "file" : "NotoSerif-Black.ttf", "size" : [ 18, 36, 54, 72], "outline" : true },
|
||||
"MEDFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 15, 31, 46, 62] },
|
||||
"SMALFONT" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 12, 24, 36, 48] },
|
||||
"MEDFONT" : { "file" : "NotoSerif-Bold.ttf", "size" : [ 13, 26, 39, 52] },
|
||||
"SMALFONT" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 11, 22, 33, 44] },
|
||||
"TIMES08R" : { "file" : "NotoSerif-Medium.ttf", "size" : [ 8, 16, 24, 32] },
|
||||
"TINY" : { "file" : "NotoSans-Medium.ttf", "size" : [ 9, 19, 28, 38], "noShadow" : true }, // The only H3 font without shadow
|
||||
"TINY" : { "file" : "NotoSans-Medium.ttf", "size" : [ 9, 18, 28, 38], "noShadow" : true }, // The only H3 font without shadow
|
||||
"VERD10B" : { "file" : "NotoSans-Medium.ttf", "size" : [ 13, 26, 39, 52] }
|
||||
}
|
||||
}
|
||||
|
@ -509,7 +509,7 @@
|
||||
},
|
||||
"neutralAI" : {
|
||||
"type" : "string",
|
||||
"default" : "StupidAI"
|
||||
"default" : "BattleAI"
|
||||
},
|
||||
"enemyAI" : {
|
||||
"type" : "string",
|
||||
|
@ -22,6 +22,7 @@
|
||||
"minesLikeZone" : { "type" : "number" },
|
||||
"terrainTypeLikeZone" : { "type" : "number" },
|
||||
"treasureLikeZone" : { "type" : "number" },
|
||||
"customObjectsLikeZone" : { "type" : "number" },
|
||||
|
||||
"terrainTypes": {"$ref" : "#/definitions/stringArray"},
|
||||
"bannedTerrains": {"$ref" : "#/definitions/stringArray"},
|
||||
@ -49,6 +50,51 @@
|
||||
},
|
||||
"additionalProperties" : false
|
||||
}
|
||||
},
|
||||
"customObjects" : {
|
||||
"type" : "object",
|
||||
"properties": {
|
||||
"bannedCategories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["all", "dwelling", "creatureBank", "randomArtifact", "bonus", "resource", "resourceGenerator", "spellScroll", "pandorasBox", "questArtifact", "seerHut"]
|
||||
}
|
||||
},
|
||||
"bannedObjects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"commonObjects": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"rmg": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"type": "integer"
|
||||
},
|
||||
"rarity": {
|
||||
"type": "integer"
|
||||
},
|
||||
"zoneLimit": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["value", "rarity"]
|
||||
}
|
||||
},
|
||||
"required": ["id", "rmg"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -99,10 +99,13 @@
|
||||
"minesLikeZone" : 1,
|
||||
|
||||
// Treasures will have same configuration as in linked zone
|
||||
"treasureLikeZone" : 1
|
||||
"treasureLikeZone" : 1,
|
||||
|
||||
// Terrain type will have same configuration as in linked zone
|
||||
"terrainTypeLikeZone" : 3
|
||||
"terrainTypeLikeZone" : 3,
|
||||
|
||||
// Custom objects will have same configuration as in linked zone
|
||||
"customObjectsLikeZone" : 1,
|
||||
|
||||
// factions of monsters allowed on this zone
|
||||
"allowedMonsters" : ["inferno", "necropolis"]
|
||||
@ -130,6 +133,28 @@
|
||||
"density" : 5
|
||||
}
|
||||
...
|
||||
]
|
||||
],
|
||||
|
||||
// Objects with different configuration than default / set by mods
|
||||
"customObjects" :
|
||||
{
|
||||
// All of objects of this kind will be removed from zone
|
||||
// Possible values: "all", "none", "creatureBank", "bonus", "dwelling", "resource", "resourceGenerator", "spellScroll", "randomArtifact", "pandorasBox", "questArtifact", "seerHut", "other
|
||||
"bannedCategories" : ["all", "dwelling", "creatureBank", "other"],
|
||||
// Specify object types and subtypes
|
||||
"bannedObjects" :["core:object.randomArtifactRelic"],
|
||||
// Configure individual common objects - overrides banned objects
|
||||
"commonObjects":
|
||||
[
|
||||
{
|
||||
"id" : "core:object.creatureBank.dragonFlyHive",
|
||||
"rmg" : {
|
||||
"value" : 9000,
|
||||
"rarity" : 500,
|
||||
"zoneLimit" : 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
@ -442,17 +442,17 @@
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="648"/>
|
||||
<source>Gog files</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Arquivos GOG</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="650"/>
|
||||
<source>All files (*.*)</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Todos os arquivos (*.*)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="652"/>
|
||||
<source>Select files (configs, mods, maps, campaigns, gog files) to install...</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Selecione arquivos (configurações, mods, mapas, campanhas, arquivos gog) para instalar...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="677"/>
|
||||
@ -483,7 +483,7 @@ Encountered errors:
|
||||
</source>
|
||||
<translation>Não foi possível baixar todos os arquivos.
|
||||
|
||||
Encontrados os seguintes erros:
|
||||
Erros encontrados:
|
||||
|
||||
</translation>
|
||||
</message>
|
||||
@ -494,12 +494,12 @@ Encontrados os seguintes erros:
|
||||
Install successfully downloaded?</source>
|
||||
<translation>
|
||||
|
||||
Instalar o download realizado com sucesso?</translation>
|
||||
O download da instalação foi bem-sucedido?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="852"/>
|
||||
<source>Installing chronicles</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Instalando crônicas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/cmodlistview_moc.cpp" line="925"/>
|
||||
@ -641,12 +641,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="307"/>
|
||||
<source>Artificial Intelligence</source>
|
||||
<translation>Inteligência Artificial</translation>
|
||||
<translation>Inteligência artificial</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1072"/>
|
||||
<source>Interface Scaling</source>
|
||||
<translation>Escala da Interface</translation>
|
||||
<translation>Escala da interface</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="924"/>
|
||||
@ -666,12 +666,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="931"/>
|
||||
<source>Adventure Map Allies</source>
|
||||
<translation>Aliados do Mapa de Aventura</translation>
|
||||
<translation>Aliados do mapa de aventura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="490"/>
|
||||
<source>Online Lobby port</source>
|
||||
<translation>Porta da Sala de Espera On-line</translation>
|
||||
<translation>Porta da sala de espera on-line</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="331"/>
|
||||
@ -681,22 +681,22 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="352"/>
|
||||
<source>Sticks Sensitivity</source>
|
||||
<translation>Sensibilidade dos Analógicos</translation>
|
||||
<translation>Sensibilidade dos analógicos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="618"/>
|
||||
<source>Automatic (Linear)</source>
|
||||
<translation>Automático (Linear)</translation>
|
||||
<translation>Automático (linear)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="798"/>
|
||||
<source>Haptic Feedback</source>
|
||||
<translation>Resposta Tátil</translation>
|
||||
<translation>Resposta tátil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="835"/>
|
||||
<source>Software Cursor</source>
|
||||
<translation>Cursor por Software</translation>
|
||||
<translation>Cursor por software</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1166"/>
|
||||
@ -723,25 +723,30 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<source>xBRZ x4</source>
|
||||
<translation>xBRZ x4</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1194"/>
|
||||
<source>Use scalable fonts</source>
|
||||
<translation>Usar fontes escaláveis</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="138"/>
|
||||
<source>Online Lobby address</source>
|
||||
<translation>Endereço da Sala de Espera On-line</translation>
|
||||
<translation>Endereço da sala de espera on-line</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1158"/>
|
||||
<source>Upscaling Filter</source>
|
||||
<translation>Filtro de Aumento de Escala</translation>
|
||||
<translation>Filtro de aumento de escala</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="317"/>
|
||||
<source>Use Relative Pointer Mode</source>
|
||||
<translation>Usar Modo de Ponteiro Relativo</translation>
|
||||
<translation>Usar modo de ponteiro relativo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="608"/>
|
||||
<source>Nearest</source>
|
||||
<translation>Mais Próximo</translation>
|
||||
<translation>Mais próximo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="613"/>
|
||||
@ -751,28 +756,23 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="750"/>
|
||||
<source>Input - Touchscreen</source>
|
||||
<translation>Entrada - Tela de Toque</translation>
|
||||
<translation>Entrada - tela de toque</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="900"/>
|
||||
<source>Adventure Map Enemies</source>
|
||||
<translation>Inimigos do Mapa de Aventura</translation>
|
||||
<translation>Inimigos do mapa de aventura</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1144"/>
|
||||
<source>Show Tutorial again</source>
|
||||
<translation>Mostrar o Tutorial novamente</translation>
|
||||
<translation>Mostrar o tutorial novamente</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1151"/>
|
||||
<source>Reset</source>
|
||||
<translation>Redefinir</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1194"/>
|
||||
<source>Use scalable fonts</source>
|
||||
<translation>Usar Fontes Escaláveis</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="854"/>
|
||||
<source>Network</source>
|
||||
@ -786,12 +786,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="842"/>
|
||||
<source>Relative Pointer Speed</source>
|
||||
<translation>Velocidade do Ponteiro Relativo</translation>
|
||||
<translation>Velocidade do ponteiro relativo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1137"/>
|
||||
<source>Music Volume</source>
|
||||
<translation>Volume da Música</translation>
|
||||
<translation>Volume da música</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="767"/>
|
||||
@ -801,12 +801,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="943"/>
|
||||
<source>Input - Mouse</source>
|
||||
<translation>Entrada - Mouse</translation>
|
||||
<translation>Entrada - mouse</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="345"/>
|
||||
<source>Long Touch Duration</source>
|
||||
<translation>Duração do Toque Longo</translation>
|
||||
<translation>Duração do toque longo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="115"/>
|
||||
@ -816,22 +816,22 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1037"/>
|
||||
<source>Controller Click Tolerance</source>
|
||||
<translation>Tolerância de Clique do Controle</translation>
|
||||
<translation>Tolerância de clique do controle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="359"/>
|
||||
<source>Touch Tap Tolerance</source>
|
||||
<translation>Tolerância de Toque Tátil</translation>
|
||||
<translation>Tolerância de toque tátil</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1020"/>
|
||||
<source>Input - Controller</source>
|
||||
<translation>Entrada - Controle</translation>
|
||||
<translation>Entrada - controle</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1086"/>
|
||||
<source>Sound Volume</source>
|
||||
<translation>Volume do Som</translation>
|
||||
<translation>Volume do som</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="402"/>
|
||||
@ -856,12 +856,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="893"/>
|
||||
<source>Downscaling Filter</source>
|
||||
<translation>Filtro de Redução de Escala</translation>
|
||||
<translation>Filtro de redução de escala</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1030"/>
|
||||
<source>Framerate Limit</source>
|
||||
<translation>Limite de Taxa de Quadros</translation>
|
||||
<translation>Limite de taxa de quadros</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="760"/>
|
||||
@ -871,12 +871,12 @@ Instalar o download realizado com sucesso?</translation>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="828"/>
|
||||
<source>Mouse Click Tolerance</source>
|
||||
<translation>Tolerância de Clique do Mouse</translation>
|
||||
<translation>Tolerância de clique do mouse</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="94"/>
|
||||
<source>Sticks Acceleration</source>
|
||||
<translation>Aceleração dos Analógicos</translation>
|
||||
<translation>Aceleração dos analógicos</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.ui" line="1008"/>
|
||||
@ -1003,7 +1003,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="536"/>
|
||||
<source>Not Installed</source>
|
||||
<translation>Não Instalado</translation>
|
||||
<translation>Não instalado</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../settingsView/csettingsview_moc.cpp" line="537"/>
|
||||
@ -1016,35 +1016,35 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="48"/>
|
||||
<source>File cannot opened</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Não foi possível abrir o arquivo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="56"/>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="71"/>
|
||||
<source>Invalid file selected</source>
|
||||
<translation type="unfinished">Arquivo selecionado inválido</translation>
|
||||
<translation>Arquivo selecionado inválido</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="56"/>
|
||||
<source>You have to select an gog installer file!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Você precisa selecionar um arquivo de instalação do GOG!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="71"/>
|
||||
<source>You have to select an chronicle installer file!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Você precisa selecionar um arquivo de instalação do Chronicles!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="87"/>
|
||||
<source>Extracting error!</source>
|
||||
<translation type="unfinished">Erro ao extrair!</translation>
|
||||
<translation>Erro ao extrair!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="104"/>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="105"/>
|
||||
<location filename="../modManager/chroniclesextractor.cpp" line="141"/>
|
||||
<source>Heroes Chronicles</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Heroes Chronicles</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1090,7 +1090,7 @@ Modo de tela cheia exclusivo - o jogo cobrirá toda a sua tela e usará a resolu
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="78"/>
|
||||
<source>Mods Preset</source>
|
||||
<translation>Predefinição de Mod</translation>
|
||||
<translation>Predefinição de mod</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="126"/>
|
||||
@ -1220,7 +1220,7 @@ O instalador offline consiste em duas partes, .exe e .bin. Certifique-se de baix
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="347"/>
|
||||
<source>Manual Installation</source>
|
||||
<translation>Instalação Manual</translation>
|
||||
<translation>Instalação manual</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="360"/>
|
||||
@ -1246,7 +1246,7 @@ O instalador offline consiste em duas partes, .exe e .bin. Certifique-se de baix
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="594"/>
|
||||
<source>Install VCMI Mod Preset</source>
|
||||
<translation>Instalar Predefinição de Mod do VCMI</translation>
|
||||
<translation>Instalar predefinição de mod do VCMI</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../firstLaunch/firstlaunch_moc.ui" line="710"/>
|
||||
@ -1370,7 +1370,7 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III
|
||||
<message>
|
||||
<location filename="../modManager/imageviewer_moc.ui" line="20"/>
|
||||
<source>Image Viewer</source>
|
||||
<translation>Visualizador de Imagens</translation>
|
||||
<translation>Visualizador de imagens</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1379,18 +1379,18 @@ Por favor, selecione o diretório com Heroes III: Complete Edition ou Heroes III
|
||||
<location filename="../innoextract.cpp" line="42"/>
|
||||
<source>Stream error while extracting files!
|
||||
error reason: </source>
|
||||
<translation type="unfinished">Erro de fluxo ao extrair arquivos!
|
||||
<translation>Erro de fluxo ao extrair arquivos!
|
||||
Motivo do erro: </translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../innoextract.cpp" line="55"/>
|
||||
<source>Not a supported Inno Setup installer!</source>
|
||||
<translation type="unfinished">Instalador Inno Setup não suportado!</translation>
|
||||
<translation>Instalador Inno Setup não suportado!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../innoextract.cpp" line="58"/>
|
||||
<source>VCMI was compiled without innoextract support, which is needed to extract exe files!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>O VCMI foi compilado sem suporte ao innoextract, que é necessário para extrair arquivos EXE!</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
@ -1506,7 +1506,7 @@ Motivo do erro: </translation>
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.ui" line="210"/>
|
||||
<source>Map Editor</source>
|
||||
<translation>Editor de Mapas</translation>
|
||||
<translation>Editor de mapas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../mainwindow_moc.ui" line="259"/>
|
||||
|
@ -209,7 +209,7 @@ DLL_LINKAGE std::vector<const CArtifact*> ArtifactUtils::assemblyPossibilities(
|
||||
|
||||
for(const auto constituent : artifact->getConstituents()) //check if all constituents are available
|
||||
{
|
||||
if(!artSet->hasArt(constituent->getId(), onlyEquiped, false, false))
|
||||
if(!artSet->hasArt(constituent->getId(), onlyEquiped, false))
|
||||
{
|
||||
possible = false;
|
||||
break;
|
||||
|
@ -220,7 +220,7 @@ bool CArtifact::canBePutAt(const CArtifactSet * artSet, ArtifactPosition slot, b
|
||||
auto possibleSlot = ArtifactUtils::getArtAnyPosition(&fittingSet, art->getId());
|
||||
if(ArtifactUtils::isSlotEquipment(possibleSlot))
|
||||
{
|
||||
fittingSet.setNewArtSlot(possibleSlot, nullptr, true);
|
||||
fittingSet.lockSlot(possibleSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -691,9 +691,7 @@ void CArtHandler::afterLoadFinalization()
|
||||
CBonusSystemNode::treeHasChanged();
|
||||
}
|
||||
|
||||
CArtifactSet::~CArtifactSet() = default;
|
||||
|
||||
const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const
|
||||
CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked) const
|
||||
{
|
||||
if(const ArtSlotInfo * si = getSlot(pos))
|
||||
{
|
||||
@ -704,56 +702,34 @@ const CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, boo
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CArtifactInstance * CArtifactSet::getArt(const ArtifactPosition & pos, bool excludeLocked)
|
||||
{
|
||||
return const_cast<CArtifactInstance*>((const_cast<const CArtifactSet*>(this))->getArt(pos, excludeLocked));
|
||||
}
|
||||
|
||||
ArtifactPosition CArtifactSet::getArtPos(const ArtifactID & aid, bool onlyWorn, bool allowLocked) const
|
||||
{
|
||||
const auto result = getAllArtPositions(aid, onlyWorn, allowLocked, false);
|
||||
return result.empty() ? ArtifactPosition{ArtifactPosition::PRE_FIRST} : result[0];
|
||||
}
|
||||
|
||||
std::vector<ArtifactPosition> CArtifactSet::getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const
|
||||
{
|
||||
std::vector<ArtifactPosition> result;
|
||||
for(const auto & slotInfo : artifactsWorn)
|
||||
if(slotInfo.second.artifact->getTypeId() == aid && (allowLocked || !slotInfo.second.locked))
|
||||
result.push_back(slotInfo.first);
|
||||
|
||||
if(onlyWorn)
|
||||
return result;
|
||||
if(!getAll && !result.empty())
|
||||
return result;
|
||||
|
||||
auto backpackPositions = getBackpackArtPositions(aid);
|
||||
result.insert(result.end(), backpackPositions.begin(), backpackPositions.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ArtifactPosition> CArtifactSet::getBackpackArtPositions(const ArtifactID & aid) const
|
||||
{
|
||||
std::vector<ArtifactPosition> result;
|
||||
|
||||
si32 backpackPosition = ArtifactPosition::BACKPACK_START;
|
||||
for(const auto & artInfo : artifactsInBackpack)
|
||||
for(const auto & [slot, slotInfo] : artifactsWorn)
|
||||
{
|
||||
const auto * art = artInfo.getArt();
|
||||
if(art && art->artType->getId() == aid)
|
||||
result.emplace_back(backpackPosition);
|
||||
backpackPosition++;
|
||||
if(slotInfo.artifact->getTypeId() == aid && (allowLocked || !slotInfo.locked))
|
||||
return slot;
|
||||
}
|
||||
return result;
|
||||
if(!onlyWorn)
|
||||
{
|
||||
size_t backpackPositionIdx = ArtifactPosition::BACKPACK_START;
|
||||
for(const auto & artInfo : artifactsInBackpack)
|
||||
{
|
||||
const auto art = artInfo.getArt();
|
||||
if(art && art->artType->getId() == aid)
|
||||
return ArtifactPosition(backpackPositionIdx);
|
||||
backpackPositionIdx++;
|
||||
}
|
||||
}
|
||||
return ArtifactPosition::PRE_FIRST;
|
||||
}
|
||||
|
||||
const CArtifactInstance * CArtifactSet::getArtByInstanceId(const ArtifactInstanceID & artInstId) const
|
||||
{
|
||||
for(auto i : artifactsWorn)
|
||||
for(const auto & i : artifactsWorn)
|
||||
if(i.second.artifact->getId() == artInstId)
|
||||
return i.second.artifact;
|
||||
|
||||
for(auto i : artifactsInBackpack)
|
||||
for(const auto & i : artifactsInBackpack)
|
||||
if(i.artifact->getId() == artInstId)
|
||||
return i.artifact;
|
||||
|
||||
@ -779,29 +755,16 @@ ArtifactPosition CArtifactSet::getArtPos(const CArtifactInstance * artInst) cons
|
||||
return ArtifactPosition::PRE_FIRST;
|
||||
}
|
||||
|
||||
bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const
|
||||
bool CArtifactSet::hasArt(const ArtifactID & aid, bool onlyWorn, bool searchCombinedParts) const
|
||||
{
|
||||
return getArtPosCount(aid, onlyWorn, searchBackpackAssemblies, allowLocked) > 0;
|
||||
if(searchCombinedParts && getCombinedArtWithPart(aid))
|
||||
return true;
|
||||
if(getArtPos(aid, onlyWorn, searchCombinedParts) != ArtifactPosition::PRE_FIRST)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CArtifactSet::hasArtBackpack(const ArtifactID & aid) const
|
||||
{
|
||||
return !getBackpackArtPositions(aid).empty();
|
||||
}
|
||||
|
||||
unsigned CArtifactSet::getArtPosCount(const ArtifactID & aid, bool onlyWorn, bool searchBackpackAssemblies, bool allowLocked) const
|
||||
{
|
||||
const auto allPositions = getAllArtPositions(aid, onlyWorn, allowLocked, true);
|
||||
if(!allPositions.empty())
|
||||
return allPositions.size();
|
||||
|
||||
if(searchBackpackAssemblies && getHiddenArt(aid))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(ArtifactPosition slot, CArtifactInstance * art)
|
||||
CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(const ArtifactPosition & slot, CArtifactInstance * art)
|
||||
{
|
||||
ArtPlacementMap resArtPlacement;
|
||||
|
||||
@ -827,19 +790,38 @@ CArtifactSet::ArtPlacementMap CArtifactSet::putArtifact(ArtifactPosition slot, C
|
||||
|
||||
assert(ArtifactUtils::isSlotEquipment(partSlot));
|
||||
setNewArtSlot(partSlot, part.art, true);
|
||||
resArtPlacement.emplace(std::make_pair(part.art, partSlot));
|
||||
resArtPlacement.emplace(part.art, partSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
resArtPlacement.emplace(std::make_pair(part.art, part.slot));
|
||||
resArtPlacement.emplace(part.art, part.slot);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resArtPlacement;
|
||||
}
|
||||
|
||||
void CArtifactSet::removeArtifact(ArtifactPosition slot)
|
||||
void CArtifactSet::removeArtifact(const ArtifactPosition & slot)
|
||||
{
|
||||
const auto eraseArtSlot = [this](const ArtifactPosition & slotForErase)
|
||||
{
|
||||
if(slotForErase == ArtifactPosition::TRANSITION_POS)
|
||||
{
|
||||
artifactsTransitionPos.artifact = nullptr;
|
||||
}
|
||||
else if(ArtifactUtils::isSlotBackpack(slotForErase))
|
||||
{
|
||||
auto backpackSlot = ArtifactPosition(slotForErase - ArtifactPosition::BACKPACK_START);
|
||||
|
||||
assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end());
|
||||
artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
artifactsWorn.erase(slotForErase);
|
||||
}
|
||||
};
|
||||
|
||||
if(const auto art = getArt(slot, false))
|
||||
{
|
||||
if(art->isCombined())
|
||||
@ -858,7 +840,7 @@ void CArtifactSet::removeArtifact(ArtifactPosition slot)
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<const CArtifactInstance *, const CArtifactInstance *> CArtifactSet::searchForConstituent(const ArtifactID & aid) const
|
||||
const CArtifactInstance * CArtifactSet::getCombinedArtWithPart(const ArtifactID & partId) const
|
||||
{
|
||||
for(const auto & slot : artifactsInBackpack)
|
||||
{
|
||||
@ -867,24 +849,12 @@ std::pair<const CArtifactInstance *, const CArtifactInstance *> CArtifactSet::se
|
||||
{
|
||||
for(auto & ci : art->getPartsInfo())
|
||||
{
|
||||
if(ci.art->getTypeId() == aid)
|
||||
{
|
||||
return {art, ci.art};
|
||||
}
|
||||
if(ci.art->getTypeId() == partId)
|
||||
return art;
|
||||
}
|
||||
}
|
||||
}
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
const CArtifactInstance * CArtifactSet::getHiddenArt(const ArtifactID & aid) const
|
||||
{
|
||||
return searchForConstituent(aid).second;
|
||||
}
|
||||
|
||||
const CArtifactInstance * CArtifactSet::getAssemblyByConstituent(const ArtifactID & aid) const
|
||||
{
|
||||
return searchForConstituent(aid).first;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const
|
||||
@ -905,6 +875,11 @@ const ArtSlotInfo * CArtifactSet::getSlot(const ArtifactPosition & pos) const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CArtifactSet::lockSlot(const ArtifactPosition & pos)
|
||||
{
|
||||
setNewArtSlot(pos, nullptr, true);
|
||||
}
|
||||
|
||||
bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck) const
|
||||
{
|
||||
if(bearerType() == ArtBearer::ALTAR)
|
||||
@ -916,7 +891,7 @@ bool CArtifactSet::isPositionFree(const ArtifactPosition & pos, bool onlyLockChe
|
||||
return true; //no slot means not used
|
||||
}
|
||||
|
||||
void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, ConstTransitivePtr<CArtifactInstance> art, bool locked)
|
||||
void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked)
|
||||
{
|
||||
assert(!vstd::contains(artifactsWorn, slot));
|
||||
|
||||
@ -932,31 +907,12 @@ void CArtifactSet::setNewArtSlot(const ArtifactPosition & slot, ConstTransitiveP
|
||||
else
|
||||
{
|
||||
auto position = artifactsInBackpack.begin() + slot - ArtifactPosition::BACKPACK_START;
|
||||
slotInfo = &(*artifactsInBackpack.emplace(position, ArtSlotInfo()));
|
||||
slotInfo = &(*artifactsInBackpack.emplace(position));
|
||||
}
|
||||
slotInfo->artifact = art;
|
||||
slotInfo->locked = locked;
|
||||
}
|
||||
|
||||
void CArtifactSet::eraseArtSlot(const ArtifactPosition & slot)
|
||||
{
|
||||
if(slot == ArtifactPosition::TRANSITION_POS)
|
||||
{
|
||||
artifactsTransitionPos.artifact = nullptr;
|
||||
}
|
||||
else if(ArtifactUtils::isSlotBackpack(slot))
|
||||
{
|
||||
auto backpackSlot = ArtifactPosition(slot - ArtifactPosition::BACKPACK_START);
|
||||
|
||||
assert(artifactsInBackpack.begin() + backpackSlot < artifactsInBackpack.end());
|
||||
artifactsInBackpack.erase(artifactsInBackpack.begin() + backpackSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
artifactsWorn.erase(slot);
|
||||
}
|
||||
}
|
||||
|
||||
void CArtifactSet::artDeserializationFix(CBonusSystemNode *node)
|
||||
{
|
||||
for(auto & elem : artifactsWorn)
|
||||
|
@ -175,10 +175,10 @@ private:
|
||||
|
||||
struct DLL_LINKAGE ArtSlotInfo
|
||||
{
|
||||
ConstTransitivePtr<CArtifactInstance> artifact;
|
||||
ui8 locked; //if locked, then artifact points to the combined artifact
|
||||
CArtifactInstance * artifact;
|
||||
bool locked; //if locked, then artifact points to the combined artifact
|
||||
|
||||
ArtSlotInfo() : locked(false) {}
|
||||
ArtSlotInfo() : artifact(nullptr), locked(false) {}
|
||||
const CArtifactInstance * getArt() const;
|
||||
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
@ -197,32 +197,20 @@ public:
|
||||
std::map<ArtifactPosition, ArtSlotInfo> artifactsWorn; //map<position,artifact_id>; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5
|
||||
ArtSlotInfo artifactsTransitionPos; // Used as transition position for dragAndDrop artifact exchange
|
||||
|
||||
void setNewArtSlot(const ArtifactPosition & slot, ConstTransitivePtr<CArtifactInstance> art, bool locked);
|
||||
void eraseArtSlot(const ArtifactPosition & slot);
|
||||
|
||||
const ArtSlotInfo * getSlot(const ArtifactPosition & pos) const;
|
||||
const CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true) const; //nullptr - no artifact
|
||||
CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true); //nullptr - no artifact
|
||||
/// Looks for equipped artifact with given ID and returns its slot ID or -1 if none
|
||||
/// (if more than one such artifact lower ID is returned)
|
||||
void lockSlot(const ArtifactPosition & pos);
|
||||
CArtifactInstance * getArt(const ArtifactPosition & pos, bool excludeLocked = true) const;
|
||||
/// Looks for first artifact with given ID
|
||||
ArtifactPosition getArtPos(const ArtifactID & aid, bool onlyWorn = true, bool allowLocked = true) const;
|
||||
ArtifactPosition getArtPos(const CArtifactInstance * art) const;
|
||||
std::vector<ArtifactPosition> getAllArtPositions(const ArtifactID & aid, bool onlyWorn, bool allowLocked, bool getAll) const;
|
||||
std::vector<ArtifactPosition> getBackpackArtPositions(const ArtifactID & aid) const;
|
||||
const CArtifactInstance * getArtByInstanceId(const ArtifactInstanceID & artInstId) const;
|
||||
/// Search for constituents of assemblies in backpack which do not have an ArtifactPosition
|
||||
const CArtifactInstance * getHiddenArt(const ArtifactID & aid) const;
|
||||
const CArtifactInstance * getAssemblyByConstituent(const ArtifactID & aid) const;
|
||||
/// Checks if hero possess artifact of given id (either in backack or worn)
|
||||
bool hasArt(const ArtifactID & aid, bool onlyWorn = false, bool searchBackpackAssemblies = false, bool allowLocked = true) const;
|
||||
bool hasArtBackpack(const ArtifactID & aid) const;
|
||||
bool hasArt(const ArtifactID & aid, bool onlyWorn = false, bool searchCombinedParts = false) const;
|
||||
bool isPositionFree(const ArtifactPosition & pos, bool onlyLockCheck = false) const;
|
||||
unsigned getArtPosCount(const ArtifactID & aid, bool onlyWorn = true, bool searchBackpackAssemblies = true, bool allowLocked = true) const;
|
||||
|
||||
virtual ArtBearer::ArtBearer bearerType() const = 0;
|
||||
virtual ArtPlacementMap putArtifact(ArtifactPosition slot, CArtifactInstance * art);
|
||||
virtual void removeArtifact(ArtifactPosition slot);
|
||||
virtual ~CArtifactSet();
|
||||
virtual ArtPlacementMap putArtifact(const ArtifactPosition & slot, CArtifactInstance * art);
|
||||
virtual void removeArtifact(const ArtifactPosition & slot);
|
||||
virtual ~CArtifactSet() = default;
|
||||
|
||||
template <typename Handler> void serialize(Handler &h)
|
||||
{
|
||||
@ -233,10 +221,11 @@ public:
|
||||
void artDeserializationFix(CBonusSystemNode *node);
|
||||
|
||||
void serializeJsonArtifacts(JsonSerializeFormat & handler, const std::string & fieldName);
|
||||
protected:
|
||||
std::pair<const CArtifactInstance *, const CArtifactInstance *> searchForConstituent(const ArtifactID & aid) const;
|
||||
const CArtifactInstance * getCombinedArtWithPart(const ArtifactID & partId) const;
|
||||
|
||||
private:
|
||||
void setNewArtSlot(const ArtifactPosition & slot, CArtifactInstance * art, bool locked);
|
||||
|
||||
void serializeJsonHero(JsonSerializeFormat & handler);
|
||||
void serializeJsonCreature(JsonSerializeFormat & handler);
|
||||
void serializeJsonCommander(JsonSerializeFormat & handler);
|
||||
|
@ -49,13 +49,13 @@ const std::vector<CCombinedArtifactInstance::PartInfo> & CCombinedArtifactInstan
|
||||
return partsInfo;
|
||||
}
|
||||
|
||||
void CCombinedArtifactInstance::addPlacementMap(CArtifactSet::ArtPlacementMap & placementMap)
|
||||
void CCombinedArtifactInstance::addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap)
|
||||
{
|
||||
if(!placementMap.empty())
|
||||
for(auto & part : partsInfo)
|
||||
{
|
||||
assert(placementMap.find(part.art) != placementMap.end());
|
||||
part.slot = placementMap.at(part.art);
|
||||
if(placementMap.find(part.art) != placementMap.end())
|
||||
part.slot = placementMap.at(part.art);
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,28 +167,6 @@ bool CArtifactInstance::isScroll() const
|
||||
return artType->isScroll();
|
||||
}
|
||||
|
||||
void CArtifactInstance::putAt(CArtifactSet & set, const ArtifactPosition slot)
|
||||
{
|
||||
auto placementMap = set.putArtifact(slot, this);
|
||||
addPlacementMap(placementMap);
|
||||
}
|
||||
|
||||
void CArtifactInstance::removeFrom(CArtifactSet & set, const ArtifactPosition slot)
|
||||
{
|
||||
set.removeArtifact(slot);
|
||||
for(auto & part : partsInfo)
|
||||
{
|
||||
if(part.slot != ArtifactPosition::PRE_FIRST)
|
||||
part.slot = ArtifactPosition::PRE_FIRST;
|
||||
}
|
||||
}
|
||||
|
||||
void CArtifactInstance::move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot)
|
||||
{
|
||||
removeFrom(srcSet, srcSlot);
|
||||
putAt(dstSet, dstSlot);
|
||||
}
|
||||
|
||||
void CArtifactInstance::deserializationFix()
|
||||
{
|
||||
setType(artType);
|
||||
|
@ -25,7 +25,7 @@ protected:
|
||||
public:
|
||||
struct PartInfo
|
||||
{
|
||||
ConstTransitivePtr<CArtifactInstance> art;
|
||||
CArtifactInstance * art;
|
||||
ArtifactPosition slot;
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
@ -39,7 +39,7 @@ public:
|
||||
// Checks if supposed part inst is part of this combined art inst
|
||||
bool isPart(const CArtifactInstance * supposedPart) const;
|
||||
const std::vector<PartInfo> & getPartsInfo() const;
|
||||
void addPlacementMap(CArtifactSet::ArtPlacementMap & placementMap);
|
||||
void addPlacementMap(const CArtifactSet::ArtPlacementMap & placementMap);
|
||||
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
{
|
||||
@ -88,9 +88,6 @@ public:
|
||||
bool assumeDestRemoved = false) const;
|
||||
bool isCombined() const;
|
||||
bool isScroll() const;
|
||||
void putAt(CArtifactSet & set, const ArtifactPosition slot);
|
||||
void removeFrom(CArtifactSet & set, const ArtifactPosition slot);
|
||||
void move(CArtifactSet & srcSet, const ArtifactPosition srcSlot, CArtifactSet & dstSet, const ArtifactPosition dstSlot);
|
||||
|
||||
void deserializationFix();
|
||||
template <typename Handler> void serialize(Handler & h)
|
||||
|
@ -877,7 +877,7 @@ ArtBearer::ArtBearer CStackInstance::bearerType() const
|
||||
return ArtBearer::CREATURE;
|
||||
}
|
||||
|
||||
CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
|
||||
CStackInstance::ArtPlacementMap CStackInstance::putArtifact(const ArtifactPosition & pos, CArtifactInstance * art)
|
||||
{
|
||||
assert(!getArt(pos));
|
||||
assert(art->canBePutAt(this, pos));
|
||||
@ -886,7 +886,7 @@ CStackInstance::ArtPlacementMap CStackInstance::putArtifact(ArtifactPosition pos
|
||||
return CArtifactSet::putArtifact(pos, art);
|
||||
}
|
||||
|
||||
void CStackInstance::removeArtifact(ArtifactPosition pos)
|
||||
void CStackInstance::removeArtifact(const ArtifactPosition & pos)
|
||||
{
|
||||
assert(getArt(pos));
|
||||
|
||||
|
@ -128,8 +128,8 @@ public:
|
||||
void setArmyObj(const CArmedInstance *ArmyObj);
|
||||
virtual void giveStackExp(TExpType exp);
|
||||
bool valid(bool allowUnrandomized) const;
|
||||
ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override;//from CArtifactSet
|
||||
void removeArtifact(ArtifactPosition pos) override;
|
||||
ArtPlacementMap putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) override;//from CArtifactSet
|
||||
void removeArtifact(const ArtifactPosition & pos) override;
|
||||
ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet
|
||||
std::string nodeName() const override; //from CBonusSystemnode
|
||||
void deserializationFix();
|
||||
|
@ -184,6 +184,8 @@ set(lib_MAIN_SRCS
|
||||
rmg/TileInfo.cpp
|
||||
rmg/Zone.cpp
|
||||
rmg/Functions.cpp
|
||||
rmg/ObjectInfo.cpp
|
||||
rmg/ObjectConfig.cpp
|
||||
rmg/RmgMap.cpp
|
||||
rmg/PenroseTiling.cpp
|
||||
rmg/modificators/Modificator.cpp
|
||||
@ -510,6 +512,7 @@ set(lib_MAIN_HEADERS
|
||||
mapObjects/IOwnableObject.h
|
||||
mapObjects/MapObjects.h
|
||||
mapObjects/MiscObjects.h
|
||||
mapObjects/CompoundMapObjectID.h
|
||||
mapObjects/ObjectTemplate.h
|
||||
mapObjects/ObstacleSetHandler.h
|
||||
|
||||
@ -587,6 +590,8 @@ set(lib_MAIN_HEADERS
|
||||
rmg/RmgMap.h
|
||||
rmg/float3.h
|
||||
rmg/Functions.h
|
||||
rmg/ObjectInfo.h
|
||||
rmg/ObjectConfig.h
|
||||
rmg/PenroseTiling.h
|
||||
rmg/modificators/Modificator.h
|
||||
rmg/modificators/ObjectManager.h
|
||||
|
@ -122,7 +122,7 @@ public:
|
||||
|
||||
virtual bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) = 0;
|
||||
virtual bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) = 0;
|
||||
virtual bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble = std::nullopt) = 0;
|
||||
virtual bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional<bool> askAssemble = std::nullopt) = 0;
|
||||
virtual void removeArtifact(const ArtifactLocation& al) = 0;
|
||||
virtual bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) = 0;
|
||||
|
||||
|
@ -267,6 +267,8 @@ void CGameState::updateOnLoad(StartInfo * si)
|
||||
for(auto & i : si->playerInfos)
|
||||
gs->players[i.first].human = i.second.isControlledByHuman();
|
||||
scenarioOps->extraOptionsInfo = si->extraOptionsInfo;
|
||||
scenarioOps->turnTimerInfo = si->turnTimerInfo;
|
||||
scenarioOps->simturnsInfo = si->simturnsInfo;
|
||||
}
|
||||
|
||||
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
|
||||
@ -1638,7 +1640,7 @@ bool CGameState::giveHeroArtifact(CGHeroInstance * h, const ArtifactID & aid)
|
||||
auto slot = ArtifactUtils::getArtAnyPosition(h, aid);
|
||||
if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
|
||||
{
|
||||
ai->putAt(*h, slot);
|
||||
map->putArtifactInstance(*h, ai, slot);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
|
@ -147,7 +147,7 @@ void CGameStateCampaign::trimCrossoverHeroesParameters(const CampaignTravel & tr
|
||||
if (!locked && !takeable)
|
||||
{
|
||||
logGlobal->debug("Removing artifact %s from slot %d of hero %s", art->artType->getJsonKey(), al.slot.getNum(), hero.hero->getHeroTypeName());
|
||||
hero.hero->getArt(al.slot)->removeFrom(*hero.hero, al.slot);
|
||||
gameState->map->removeArtifactInstance(*hero.hero, al.slot);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -327,7 +327,7 @@ void CGameStateCampaign::giveCampaignBonusToHero(CGHeroInstance * hero)
|
||||
CArtifactInstance * scroll = ArtifactUtils::createScroll(SpellID(curBonus->info2));
|
||||
const auto slot = ArtifactUtils::getArtAnyPosition(hero, scroll->getTypeId());
|
||||
if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
|
||||
scroll->putAt(*hero, slot);
|
||||
gameState->map->putArtifactInstance(*hero, scroll, slot);
|
||||
else
|
||||
logGlobal->error("Cannot give starting scroll - no free slots!");
|
||||
break;
|
||||
@ -423,7 +423,7 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO
|
||||
auto * artifact = donorHero->getArt(artLocation);
|
||||
|
||||
logGlobal->debug("Removing artifact %s from slot %d of hero %s for transfer", artifact->artType->getJsonKey(), artLocation.getNum(), donorHero->getHeroTypeName());
|
||||
artifact->removeFrom(*donorHero, artLocation);
|
||||
gameState->map->removeArtifactInstance(*donorHero, artLocation);
|
||||
|
||||
if (receiver)
|
||||
{
|
||||
@ -431,7 +431,7 @@ void CGameStateCampaign::transferMissingArtifacts(const CampaignTravel & travelO
|
||||
|
||||
const auto slot = ArtifactUtils::getArtAnyPosition(receiver, artifact->getTypeId());
|
||||
if(ArtifactUtils::isSlotEquipment(slot) || ArtifactUtils::isSlotBackpack(slot))
|
||||
artifact->putAt(*receiver, slot);
|
||||
gameState->map->putArtifactInstance(*receiver, artifact, slot);
|
||||
else
|
||||
logGlobal->error("Cannot transfer artifact - no free slots!");
|
||||
}
|
||||
|
@ -40,6 +40,8 @@
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../texts/CLegacyConfigParser.h"
|
||||
|
||||
#include <vstd/StringUtils.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
CObjectClassesHandler::CObjectClassesHandler()
|
||||
@ -390,6 +392,62 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(CompoundMapObjectID comp
|
||||
return getHandlerFor(compoundIdentifier.primaryID, compoundIdentifier.secondaryID);
|
||||
}
|
||||
|
||||
CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const
|
||||
{
|
||||
std::optional<si32> id;
|
||||
if (scope.empty())
|
||||
{
|
||||
id = VLC->identifiers()->getIdentifier("object", type);
|
||||
}
|
||||
else
|
||||
{
|
||||
id = VLC->identifiers()->getIdentifier(scope, "object", type);
|
||||
}
|
||||
|
||||
if(id)
|
||||
{
|
||||
if (subtype.empty())
|
||||
return CompoundMapObjectID(id.value(), 0);
|
||||
|
||||
const auto & object = mapObjectTypes.at(id.value());
|
||||
std::optional<si32> subID = VLC->identifiers()->getIdentifier(scope, object->getJsonKey(), subtype);
|
||||
|
||||
if (subID)
|
||||
return CompoundMapObjectID(id.value(), subID.value());
|
||||
}
|
||||
|
||||
std::string errorString = "Failed to get id for object of type " + type + "." + subtype;
|
||||
logGlobal->error(errorString);
|
||||
throw std::runtime_error(errorString);
|
||||
}
|
||||
|
||||
CompoundMapObjectID CObjectClassesHandler::getCompoundIdentifier(const std::string & objectName) const
|
||||
{
|
||||
std::string subtype = "object"; //Default for objects with no subIds
|
||||
std::string type;
|
||||
|
||||
auto scopeAndFullName = vstd::splitStringToPair(objectName, ':');
|
||||
logGlobal->debug("scopeAndFullName: %s, %s", scopeAndFullName.first, scopeAndFullName.second);
|
||||
|
||||
auto typeAndName = vstd::splitStringToPair(scopeAndFullName.second, '.');
|
||||
logGlobal->debug("typeAndName: %s, %s", typeAndName.first, typeAndName.second);
|
||||
|
||||
auto nameAndSubtype = vstd::splitStringToPair(typeAndName.second, '.');
|
||||
logGlobal->debug("nameAndSubtype: %s, %s", nameAndSubtype.first, nameAndSubtype.second);
|
||||
|
||||
if (!nameAndSubtype.first.empty())
|
||||
{
|
||||
type = nameAndSubtype.first;
|
||||
subtype = nameAndSubtype.second;
|
||||
}
|
||||
else
|
||||
{
|
||||
type = typeAndName.second;
|
||||
}
|
||||
|
||||
return getCompoundIdentifier(boost::to_lower_copy(scopeAndFullName.first), type, subtype);
|
||||
}
|
||||
|
||||
std::set<MapObjectID> CObjectClassesHandler::knownObjects() const
|
||||
{
|
||||
std::set<MapObjectID> ret;
|
||||
@ -459,6 +517,18 @@ void CObjectClassesHandler::afterLoadFinalization()
|
||||
logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey());
|
||||
}
|
||||
}
|
||||
|
||||
for(auto & entry : objectIdHandlers)
|
||||
{
|
||||
// Call function for each object id
|
||||
entry.second(entry.first);
|
||||
}
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback)
|
||||
{
|
||||
auto compoundId = getCompoundIdentifier(id);
|
||||
objectIdHandlers.push_back(std::make_pair(compoundId, callback));
|
||||
}
|
||||
|
||||
void CObjectClassesHandler::generateExtraMonolithsForRMG(ObjectClass * container)
|
||||
|
@ -9,7 +9,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
#include "../IHandlerBase.h"
|
||||
#include "../json/JsonNode.h"
|
||||
|
||||
@ -19,27 +19,6 @@ class AObjectTypeHandler;
|
||||
class ObjectTemplate;
|
||||
struct SObjectSounds;
|
||||
|
||||
struct DLL_LINKAGE CompoundMapObjectID
|
||||
{
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
|
||||
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
|
||||
|
||||
bool operator<(const CompoundMapObjectID& other) const
|
||||
{
|
||||
if(this->primaryID != other.primaryID)
|
||||
return this->primaryID < other.primaryID;
|
||||
else
|
||||
return this->secondaryID < other.secondaryID;
|
||||
}
|
||||
|
||||
bool operator==(const CompoundMapObjectID& other) const
|
||||
{
|
||||
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
|
||||
}
|
||||
};
|
||||
|
||||
class CGObjectInstance;
|
||||
|
||||
using TObjectTypeHandler = std::shared_ptr<AObjectTypeHandler>;
|
||||
@ -74,6 +53,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase, boost::noncopyabl
|
||||
/// map that is filled during construction with all known handlers. Not serializeable due to usage of std::function
|
||||
std::map<std::string, std::function<TObjectTypeHandler()> > handlerConstructors;
|
||||
|
||||
std::vector<std::pair<CompoundMapObjectID, std::function<void(CompoundMapObjectID)>>> objectIdHandlers;
|
||||
|
||||
/// container with H3 templates, used only during loading, no need to serialize it
|
||||
using TTemplatesContainer = std::multimap<std::pair<MapObjectID, MapObjectSubID>, std::shared_ptr<const ObjectTemplate>>;
|
||||
TTemplatesContainer legacyTemplates;
|
||||
@ -110,15 +91,19 @@ public:
|
||||
TObjectTypeHandler getHandlerFor(MapObjectID type, MapObjectSubID subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(const std::string & scope, const std::string & type, const std::string & subtype) const;
|
||||
TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const;
|
||||
CompoundMapObjectID getCompoundIdentifier(const std::string & scope, const std::string & type, const std::string & subtype) const;
|
||||
CompoundMapObjectID getCompoundIdentifier(const std::string & objectName) const;
|
||||
|
||||
std::string getObjectName(MapObjectID type, MapObjectSubID subtype) const;
|
||||
|
||||
SObjectSounds getObjectSounds(MapObjectID type, MapObjectSubID subtype) const;
|
||||
|
||||
void resolveObjectCompoundId(const std::string & id, std::function<void(CompoundMapObjectID)> callback);
|
||||
|
||||
/// Returns handler string describing the handler (for use in client)
|
||||
std::string getObjectHandlerName(MapObjectID type) const;
|
||||
|
||||
std::string getJsonKey(MapObjectID type) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -49,7 +49,10 @@ public:
|
||||
|
||||
virtual bool givesBonuses() const { return false; }
|
||||
|
||||
virtual bool hasGuards() const { return false; }
|
||||
|
||||
virtual ~IObjectInfo() = default;
|
||||
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -1179,7 +1179,7 @@ std::string CGHeroInstance::getBiographyTextID() const
|
||||
return ""; //for random hero
|
||||
}
|
||||
|
||||
CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance * art)
|
||||
CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(const ArtifactPosition & pos, CArtifactInstance * art)
|
||||
{
|
||||
assert(art->canBePutAt(this, pos));
|
||||
|
||||
@ -1188,7 +1188,7 @@ CGHeroInstance::ArtPlacementMap CGHeroInstance::putArtifact(ArtifactPosition pos
|
||||
return CArtifactSet::putArtifact(pos, art);
|
||||
}
|
||||
|
||||
void CGHeroInstance::removeArtifact(ArtifactPosition pos)
|
||||
void CGHeroInstance::removeArtifact(const ArtifactPosition & pos)
|
||||
{
|
||||
auto art = getArt(pos);
|
||||
assert(art);
|
||||
@ -1224,7 +1224,7 @@ void CGHeroInstance::removeSpellbook()
|
||||
|
||||
if(hasSpellbook())
|
||||
{
|
||||
getArt(ArtifactPosition::SPELLBOOK)->removeFrom(*this, ArtifactPosition::SPELLBOOK);
|
||||
cb->removeArtifact(ArtifactLocation(this->id, ArtifactPosition::SPELLBOOK));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -243,8 +243,8 @@ public:
|
||||
void initHero(vstd::RNG & rand);
|
||||
void initHero(vstd::RNG & rand, const HeroTypeID & SUBID);
|
||||
|
||||
ArtPlacementMap putArtifact(ArtifactPosition pos, CArtifactInstance * art) override;
|
||||
void removeArtifact(ArtifactPosition pos) override;
|
||||
ArtPlacementMap putArtifact(const ArtifactPosition & pos, CArtifactInstance * art) override;
|
||||
void removeArtifact(const ArtifactPosition & pos) override;
|
||||
void initExp(vstd::RNG & rand);
|
||||
void initArmy(vstd::RNG & rand, IArmyDescriptor *dst = nullptr);
|
||||
void pushPrimSkill(PrimarySkill which, int val);
|
||||
|
@ -152,7 +152,7 @@ void CQuest::completeQuest(IGameCallback * cb, const CGHeroInstance *h) const
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto * assembly = h->getAssemblyByConstituent(elem);
|
||||
const auto * assembly = h->getCombinedArtWithPart(elem);
|
||||
assert(assembly);
|
||||
auto parts = assembly->getPartsInfo();
|
||||
|
||||
|
@ -12,99 +12,33 @@
|
||||
#include "CRewardableObject.h"
|
||||
|
||||
#include "../CPlayerState.h"
|
||||
#include "../GameSettings.h"
|
||||
#include "../IGameCallback.h"
|
||||
#include "../IGameSettings.h"
|
||||
#include "../battle/BattleLayout.h"
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/CRewardableConstructor.h"
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../networkPacks/PacksForClientBattle.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
void CRewardableObject::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const
|
||||
const IObjectInterface * CRewardableObject::getObject() const
|
||||
{
|
||||
auto vi = configuration.info.at(index);
|
||||
logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
|
||||
// show message only if it is not empty or in infobox
|
||||
if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = contextHero->tempOwner;
|
||||
iw.text = vi.message;
|
||||
vi.reward.loadComponents(iw.components, contextHero);
|
||||
iw.type = configuration.infoWindowType;
|
||||
if(!iw.components.empty() || !iw.text.toString().empty())
|
||||
cb->showInfoDialog(&iw);
|
||||
}
|
||||
// grant reward afterwards. Note that it may remove object
|
||||
if(markAsVisit)
|
||||
markAsVisited(contextHero);
|
||||
grantReward(index, contextHero);
|
||||
return this;
|
||||
}
|
||||
|
||||
void CRewardableObject::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const
|
||||
void CRewardableObject::markAsScouted(const CGHeroInstance * hero) const
|
||||
{
|
||||
BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1);
|
||||
sd.player = contextHero->tempOwner;
|
||||
sd.text = dialog;
|
||||
sd.components = loadComponents(contextHero, rewardIndices);
|
||||
cb->showBlockingDialog(this, &sd);
|
||||
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, hero->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
void CRewardableObject::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, bool markAsVisit) const
|
||||
{
|
||||
if (rewardIndices.empty())
|
||||
return;
|
||||
|
||||
for (auto index : rewardIndices)
|
||||
{
|
||||
// TODO: Merge all rewards of same type, with single message?
|
||||
grantRewardWithMessage(contextHero, index, false);
|
||||
}
|
||||
// Mark visited only after all rewards were processed
|
||||
if(markAsVisit)
|
||||
markAsVisited(contextHero);
|
||||
}
|
||||
|
||||
std::vector<Component> CRewardableObject::loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const
|
||||
{
|
||||
std::vector<Component> result;
|
||||
|
||||
if (rewardIndices.empty())
|
||||
return result;
|
||||
|
||||
if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1)
|
||||
{
|
||||
for (auto index : rewardIndices)
|
||||
result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
|
||||
}
|
||||
else
|
||||
{
|
||||
configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CRewardableObject::guardedPotentially() const
|
||||
{
|
||||
for (auto const & visitInfo : configuration.info)
|
||||
if (!visitInfo.reward.guards.empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CRewardableObject::guardedPresently() const
|
||||
bool CRewardableObject::isGuarded() const
|
||||
{
|
||||
return stacksCount() > 0;
|
||||
}
|
||||
@ -117,7 +51,7 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
if (guardedPresently())
|
||||
if (isGuarded())
|
||||
{
|
||||
auto guardedIndexes = getAvailableRewards(hero, Rewardable::EEventType::EVENT_GUARDED);
|
||||
auto guardedReward = configuration.info.at(guardedIndexes.at(0));
|
||||
@ -136,94 +70,9 @@ void CRewardableObject::onHeroVisit(const CGHeroInstance *hero) const
|
||||
}
|
||||
}
|
||||
|
||||
void CRewardableObject::doHeroVisit(const CGHeroInstance *h) const
|
||||
{
|
||||
if(!wasVisitedBefore(h))
|
||||
{
|
||||
auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
bool objectRemovalPossible = false;
|
||||
for(auto index : rewards)
|
||||
{
|
||||
if(configuration.info.at(index).reward.removeObject)
|
||||
objectRemovalPossible = true;
|
||||
}
|
||||
|
||||
logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
|
||||
switch (rewards.size())
|
||||
{
|
||||
case 0: // no available rewards, e.g. visiting School of War without gold
|
||||
{
|
||||
auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
|
||||
if (!emptyRewards.empty())
|
||||
grantRewardWithMessage(h, emptyRewards[0], false);
|
||||
else
|
||||
logMod->warn("No applicable message for visiting empty object!");
|
||||
break;
|
||||
}
|
||||
case 1: // one reward. Just give it with message
|
||||
{
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewards.front(), true);
|
||||
break;
|
||||
}
|
||||
default: // multiple rewards. Act according to select mode
|
||||
{
|
||||
switch (configuration.selectMode) {
|
||||
case Rewardable::SELECT_PLAYER: // player must select
|
||||
selectRewardWithMessage(h, rewards, configuration.onSelect);
|
||||
break;
|
||||
case Rewardable::SELECT_FIRST: // give first available
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewards.front(), true);
|
||||
break;
|
||||
case Rewardable::SELECT_RANDOM: // give random
|
||||
{
|
||||
ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator());
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewardIndex, true);
|
||||
break;
|
||||
}
|
||||
case Rewardable::SELECT_ALL: // grant all possible
|
||||
grantAllRewardsWithMessage(h, rewards, true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->debug("Revisiting already visited object");
|
||||
|
||||
if (!wasVisited(h->getOwner()))
|
||||
{
|
||||
ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD_PLAYER, id, h->id);
|
||||
cb->sendAndApply(&cov);
|
||||
}
|
||||
|
||||
auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
|
||||
if (!visitedRewards.empty())
|
||||
grantRewardWithMessage(h, visitedRewards[0], false);
|
||||
else
|
||||
logMod->warn("No applicable message for visiting already visited object!");
|
||||
}
|
||||
}
|
||||
|
||||
void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const
|
||||
{
|
||||
grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), this, hero);
|
||||
grantRewardAfterLevelup(configuration.info.at(selectedReward), this, hero);
|
||||
}
|
||||
|
||||
void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
|
||||
@ -236,7 +85,7 @@ void CRewardableObject::battleFinished(const CGHeroInstance *hero, const BattleR
|
||||
|
||||
void CRewardableObject::blockingDialogAnswered(const CGHeroInstance * hero, int32_t answer) const
|
||||
{
|
||||
if(guardedPresently())
|
||||
if(isGuarded())
|
||||
{
|
||||
if (answer)
|
||||
{
|
||||
@ -273,12 +122,12 @@ void CRewardableObject::markAsVisited(const CGHeroInstance * hero) const
|
||||
void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
|
||||
{
|
||||
cb->setObjPropertyValue(id, ObjProperty::REWARD_SELECT, rewardID);
|
||||
grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
|
||||
grantRewardBeforeLevelup(configuration.info.at(rewardID), hero);
|
||||
|
||||
// hero is not blocked by levelup dialog - grant remainder immediately
|
||||
if(!cb->isVisitCoveredByAnotherQuery(this, hero))
|
||||
{
|
||||
grantRewardAfterLevelup(cb, configuration.info.at(rewardID), this, hero);
|
||||
grantRewardAfterLevelup(configuration.info.at(rewardID), this, hero);
|
||||
}
|
||||
}
|
||||
|
||||
@ -410,7 +259,7 @@ std::vector<Component> CRewardableObject::getPopupComponentsImpl(PlayerColor pla
|
||||
if (!wasScouted(player))
|
||||
return {};
|
||||
|
||||
if (guardedPresently())
|
||||
if (isGuarded())
|
||||
{
|
||||
if (!cb->getSettings().getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION))
|
||||
return {};
|
||||
|
@ -25,32 +25,24 @@ protected:
|
||||
/// reward selected by player, no serialize
|
||||
ui16 selectedReward = 0;
|
||||
|
||||
void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
|
||||
void markAsVisited(const CGHeroInstance * hero) const;
|
||||
void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
|
||||
void markAsVisited(const CGHeroInstance * hero) const override;
|
||||
|
||||
const IObjectInterface * getObject() const override;
|
||||
void markAsScouted(const CGHeroInstance * hero) const override;
|
||||
|
||||
/// return true if this object was "cleared" before and no longer has rewards applicable to selected hero
|
||||
/// unlike wasVisited, this method uses information not available to player owner, for example, if object was cleared by another player before
|
||||
bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
|
||||
bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
|
||||
|
||||
void serializeJsonOptions(JsonSerializeFormat & handler) override;
|
||||
|
||||
virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const;
|
||||
virtual void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const;
|
||||
|
||||
virtual void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32>& rewardIndices, bool markAsVisit) const;
|
||||
|
||||
std::vector<Component> loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const;
|
||||
|
||||
std::string getDisplayTextImpl(PlayerColor player, const CGHeroInstance * hero, bool includeDescription) const;
|
||||
std::string getDescriptionMessage(PlayerColor player, const CGHeroInstance * hero) const;
|
||||
std::vector<Component> getPopupComponentsImpl(PlayerColor player, const CGHeroInstance * hero) const;
|
||||
|
||||
void doHeroVisit(const CGHeroInstance *h) const;
|
||||
|
||||
/// Returns true if this object might have guards present, whether they were cleared or not
|
||||
bool guardedPotentially() const;
|
||||
/// Returns true if this object is currently guarded
|
||||
bool guardedPresently() const;
|
||||
bool isGuarded() const;
|
||||
public:
|
||||
|
||||
/// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player)
|
||||
|
37
lib/mapObjects/CompoundMapObjectID.h
Normal file
37
lib/mapObjects/CompoundMapObjectID.h
Normal file
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* CompoundMapObjectID.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "../constants/EntityIdentifiers.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct DLL_LINKAGE CompoundMapObjectID
|
||||
{
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
|
||||
CompoundMapObjectID(si32 primID, si32 secID) : primaryID(primID), secondaryID(secID) {};
|
||||
|
||||
bool operator<(const CompoundMapObjectID& other) const
|
||||
{
|
||||
if(this->primaryID != other.primaryID)
|
||||
return this->primaryID < other.primaryID;
|
||||
else
|
||||
return this->secondaryID < other.secondaryID;
|
||||
}
|
||||
|
||||
bool operator==(const CompoundMapObjectID& other) const
|
||||
{
|
||||
return (this->primaryID == other.primaryID) && (this->secondaryID == other.secondaryID);
|
||||
}
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -772,9 +772,8 @@ void CGArtifact::initObj(vstd::RNG & rand)
|
||||
{
|
||||
if (!storedArtifact)
|
||||
{
|
||||
auto * a = new CArtifactInstance();
|
||||
cb->gameState()->map->addNewArtifactInstance(a);
|
||||
storedArtifact = a;
|
||||
storedArtifact = ArtifactUtils::createArtifact(ArtifactID());
|
||||
cb->gameState()->map->addNewArtifactInstance(storedArtifact);
|
||||
}
|
||||
if(!storedArtifact->artType)
|
||||
storedArtifact->setType(getArtifact().toArtifact());
|
||||
@ -901,7 +900,7 @@ void CGArtifact::onHeroVisit(const CGHeroInstance * h) const
|
||||
|
||||
void CGArtifact::pick(const CGHeroInstance * h) const
|
||||
{
|
||||
if(cb->putArtifact(ArtifactLocation(h->id, ArtifactPosition::FIRST_AVAILABLE), storedArtifact))
|
||||
if(cb->putArtifact(ArtifactLocation(h->id, ArtifactPosition::FIRST_AVAILABLE), storedArtifact->getId()))
|
||||
cb->removeObject(this, h->getOwner());
|
||||
}
|
||||
|
||||
|
@ -508,6 +508,11 @@ bool ObjectTemplate::canBePlacedAt(TerrainId terrainID) const
|
||||
return vstd::contains(allowedTerrains, terrainID);
|
||||
}
|
||||
|
||||
CompoundMapObjectID ObjectTemplate::getCompoundID() const
|
||||
{
|
||||
return CompoundMapObjectID(id, subid);
|
||||
}
|
||||
|
||||
void ObjectTemplate::recalculate()
|
||||
{
|
||||
calculateWidth();
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "../int3.h"
|
||||
#include "../filesystem/ResourcePath.h"
|
||||
#include "../serializer/Serializeable.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
@ -46,6 +47,7 @@ public:
|
||||
/// H3 ID/subID of this object
|
||||
MapObjectID id;
|
||||
MapObjectSubID subid;
|
||||
|
||||
/// print priority, objects with higher priority will be print first, below everything else
|
||||
si32 printPriority;
|
||||
/// animation file that should be used to display object
|
||||
@ -122,6 +124,8 @@ public:
|
||||
// Checks if object can be placed on specific terrain
|
||||
bool canBePlacedAt(TerrainId terrain) const;
|
||||
|
||||
CompoundMapObjectID getCompoundID() const;
|
||||
|
||||
ObjectTemplate();
|
||||
|
||||
void readTxt(CLegacyConfigParser & parser);
|
||||
|
@ -12,14 +12,10 @@
|
||||
#include "TownBuildingInstance.h"
|
||||
|
||||
#include "CGTownInstance.h"
|
||||
#include "../texts/CGeneralTextHandler.h"
|
||||
#include "../IGameCallback.h"
|
||||
#include "../gameState/CGameState.h"
|
||||
#include "../mapObjects/CGHeroInstance.h"
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../entities/building/CBuilding.h"
|
||||
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
@ -130,7 +126,7 @@ void TownRewardableBuildingInstance::setProperty(ObjProperty what, ObjPropertyID
|
||||
|
||||
void TownRewardableBuildingInstance::heroLevelUpDone(const CGHeroInstance *hero) const
|
||||
{
|
||||
grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero);
|
||||
grantRewardAfterLevelup(configuration.info.at(selectedReward), town, hero);
|
||||
}
|
||||
|
||||
void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance *hero, int32_t answer) const
|
||||
@ -154,14 +150,12 @@ void TownRewardableBuildingInstance::blockingDialogAnswered(const CGHeroInstance
|
||||
|
||||
void TownRewardableBuildingInstance::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
|
||||
{
|
||||
town->addHeroToStructureVisitors(hero, getBuildingType());
|
||||
|
||||
grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
|
||||
grantRewardBeforeLevelup(configuration.info.at(rewardID), hero);
|
||||
|
||||
// hero is not blocked by levelup dialog - grant remainder immediately
|
||||
if(!cb->isVisitCoveredByAnotherQuery(town, hero))
|
||||
{
|
||||
grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero);
|
||||
grantRewardAfterLevelup(configuration.info.at(rewardID), town, hero);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,93 +190,42 @@ bool TownRewardableBuildingInstance::wasVisitedBefore(const CGHeroInstance * con
|
||||
|
||||
void TownRewardableBuildingInstance::onHeroVisit(const CGHeroInstance *h) const
|
||||
{
|
||||
auto grantRewardWithMessage = [&](int index) -> void
|
||||
assert(town->hasBuilt(getBuildingType()));
|
||||
|
||||
if(town->hasBuilt(getBuildingType()))
|
||||
doHeroVisit(h);
|
||||
}
|
||||
|
||||
const IObjectInterface * TownRewardableBuildingInstance::getObject() const
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
bool TownRewardableBuildingInstance::wasVisited(PlayerColor player) const
|
||||
{
|
||||
switch (configuration.visitMode)
|
||||
{
|
||||
auto vi = configuration.info.at(index);
|
||||
logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
|
||||
|
||||
town->addHeroToStructureVisitors(h, getBuildingType()); //adding to visitors
|
||||
|
||||
InfoWindow iw;
|
||||
iw.player = h->tempOwner;
|
||||
iw.text = vi.message;
|
||||
vi.reward.loadComponents(iw.components, h);
|
||||
iw.type = EInfoWindowMode::MODAL;
|
||||
if(!iw.components.empty() || !iw.text.toString().empty())
|
||||
cb->showInfoDialog(&iw);
|
||||
|
||||
grantReward(index, h);
|
||||
};
|
||||
auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
|
||||
{
|
||||
BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
|
||||
sd.player = h->tempOwner;
|
||||
sd.text = dialog;
|
||||
|
||||
if (rewards.size() > 1)
|
||||
for (auto index : rewards)
|
||||
sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
|
||||
|
||||
if (rewards.size() == 1)
|
||||
configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
|
||||
|
||||
cb->showBlockingDialog(this, &sd);
|
||||
};
|
||||
|
||||
if(!town->hasBuilt(getBuildingType()))
|
||||
return;
|
||||
|
||||
if(!wasVisitedBefore(h))
|
||||
{
|
||||
auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
|
||||
logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
|
||||
switch (rewards.size())
|
||||
{
|
||||
case 0: // no available rewards, e.g. visiting School of War without gold
|
||||
{
|
||||
auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
|
||||
if (!emptyRewards.empty())
|
||||
grantRewardWithMessage(emptyRewards[0]);
|
||||
else
|
||||
logMod->warn("No applicable message for visiting empty object!");
|
||||
break;
|
||||
}
|
||||
case 1: // one reward. Just give it with message
|
||||
{
|
||||
if (configuration.canRefuse)
|
||||
selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
|
||||
else
|
||||
grantRewardWithMessage(rewards.front());
|
||||
break;
|
||||
}
|
||||
default: // multiple rewards. Act according to select mode
|
||||
{
|
||||
switch (configuration.selectMode) {
|
||||
case Rewardable::SELECT_PLAYER: // player must select
|
||||
selectRewardsMessage(rewards, configuration.onSelect);
|
||||
break;
|
||||
case Rewardable::SELECT_FIRST: // give first available
|
||||
grantRewardWithMessage(rewards.front());
|
||||
break;
|
||||
case Rewardable::SELECT_RANDOM: // give random
|
||||
grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case Rewardable::VISIT_UNLIMITED:
|
||||
case Rewardable::VISIT_BONUS:
|
||||
case Rewardable::VISIT_HERO:
|
||||
case Rewardable::VISIT_LIMITER:
|
||||
return false;
|
||||
case Rewardable::VISIT_ONCE:
|
||||
case Rewardable::VISIT_PLAYER:
|
||||
return !visitors.empty();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->debug("Revisiting already visited object");
|
||||
}
|
||||
|
||||
auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
|
||||
if (!visitedRewards.empty())
|
||||
grantRewardWithMessage(visitedRewards[0]);
|
||||
else
|
||||
logMod->debug("No applicable message for visiting already visited object!");
|
||||
}
|
||||
void TownRewardableBuildingInstance::markAsVisited(const CGHeroInstance * hero) const
|
||||
{
|
||||
town->addHeroToStructureVisitors(hero, getBuildingType());
|
||||
}
|
||||
|
||||
void TownRewardableBuildingInstance::markAsScouted(const CGHeroInstance * hero) const
|
||||
{
|
||||
// no-op - town building is always 'scouted' by owner
|
||||
}
|
||||
|
||||
|
||||
|
@ -63,10 +63,14 @@ class DLL_LINKAGE TownRewardableBuildingInstance : public TownBuildingInstance,
|
||||
ui16 selectedReward = 0;
|
||||
std::set<ObjectInstanceID> visitors;
|
||||
|
||||
bool wasVisitedBefore(const CGHeroInstance * contextHero) const;
|
||||
void grantReward(ui32 rewardID, const CGHeroInstance * hero) const;
|
||||
bool wasVisitedBefore(const CGHeroInstance * contextHero) const override;
|
||||
void grantReward(ui32 rewardID, const CGHeroInstance * hero) const override;
|
||||
Rewardable::Configuration generateConfiguration(vstd::RNG & rand) const;
|
||||
|
||||
const IObjectInterface * getObject() const override;
|
||||
bool wasVisited(PlayerColor player) const override;
|
||||
void markAsVisited(const CGHeroInstance * hero) const override;
|
||||
void markAsScouted(const CGHeroInstance * hero) const override;
|
||||
public:
|
||||
void setProperty(ObjProperty what, ObjPropertyID identifier) override;
|
||||
void onHeroVisit(const CGHeroInstance * h) const override;
|
||||
|
@ -552,6 +552,34 @@ void CMap::eraseArtifactInstance(CArtifactInstance * art)
|
||||
artInstances[art->getId().getNum()].dellNull();
|
||||
}
|
||||
|
||||
void CMap::moveArtifactInstance(
|
||||
CArtifactSet & srcSet, const ArtifactPosition & srcSlot,
|
||||
CArtifactSet & dstSet, const ArtifactPosition & dstSlot)
|
||||
{
|
||||
auto art = srcSet.getArt(srcSlot);
|
||||
removeArtifactInstance(srcSet, srcSlot);
|
||||
putArtifactInstance(dstSet, art, dstSlot);
|
||||
}
|
||||
|
||||
void CMap::putArtifactInstance(CArtifactSet & set, CArtifactInstance * art, const ArtifactPosition & slot)
|
||||
{
|
||||
art->addPlacementMap(set.putArtifact(slot, art));
|
||||
}
|
||||
|
||||
void CMap::removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot)
|
||||
{
|
||||
auto art = set.getArt(slot);
|
||||
assert(art);
|
||||
set.removeArtifact(slot);
|
||||
CArtifactSet::ArtPlacementMap partsMap;
|
||||
for(auto & part : art->getPartsInfo())
|
||||
{
|
||||
if(part.slot != ArtifactPosition::PRE_FIRST)
|
||||
partsMap.try_emplace(part.art, ArtifactPosition::PRE_FIRST);
|
||||
}
|
||||
art->addPlacementMap(partsMap);
|
||||
}
|
||||
|
||||
void CMap::addNewQuestInstance(CQuest* quest)
|
||||
{
|
||||
quest->qid = static_cast<si32>(quests.size());
|
||||
|
@ -110,6 +110,9 @@ public:
|
||||
void addNewArtifactInstance(CArtifactSet & artSet);
|
||||
void addNewArtifactInstance(ConstTransitivePtr<CArtifactInstance> art);
|
||||
void eraseArtifactInstance(CArtifactInstance * art);
|
||||
void moveArtifactInstance(CArtifactSet & srcSet, const ArtifactPosition & srcSlot, CArtifactSet & dstSet, const ArtifactPosition & dstSlot);
|
||||
void putArtifactInstance(CArtifactSet & set, CArtifactInstance * art, const ArtifactPosition & slot);
|
||||
void removeArtifactInstance(CArtifactSet & set, const ArtifactPosition & slot);
|
||||
|
||||
void addNewQuestInstance(CQuest * quest);
|
||||
void removeQuestInstance(CQuest * quest);
|
||||
|
@ -917,7 +917,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero)
|
||||
|
||||
hero->artifactsInBackpack.clear();
|
||||
while(!hero->artifactsWorn.empty())
|
||||
hero->eraseArtSlot(hero->artifactsWorn.begin()->first);
|
||||
hero->removeArtifact(hero->artifactsWorn.begin()->first);
|
||||
}
|
||||
|
||||
for(int i = 0; i < features.artifactSlotsCount; i++)
|
||||
@ -959,7 +959,7 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot)
|
||||
if(ArtifactID(artifactID).toArtifact()->canBePutAt(hero, ArtifactPosition(slot)))
|
||||
{
|
||||
auto * artifact = ArtifactUtils::createArtifact(artifactID);
|
||||
artifact->putAt(*hero, ArtifactPosition(slot));
|
||||
map->putArtifactInstance(*hero, artifact, slot);
|
||||
map->addNewArtifactInstance(artifact);
|
||||
}
|
||||
else
|
||||
|
@ -1471,8 +1471,7 @@ void NewArtifact::applyGs(CGameState *gs)
|
||||
{
|
||||
auto art = ArtifactUtils::createArtifact(artId, spellId);
|
||||
gs->map->addNewArtifactInstance(art);
|
||||
PutArtifact pa(ArtifactLocation(artHolder, pos), false);
|
||||
pa.art = art;
|
||||
PutArtifact pa(art->getId(), ArtifactLocation(artHolder, pos), false);
|
||||
pa.applyGs(gs);
|
||||
}
|
||||
|
||||
@ -1591,14 +1590,14 @@ void RebalanceStacks::applyGs(CGameState *gs)
|
||||
const auto dstHero = dynamic_cast<CGHeroInstance*>(dst.army.get());
|
||||
auto srcStack = const_cast<CStackInstance*>(src.getStack());
|
||||
auto dstStack = const_cast<CStackInstance*>(dst.getStack());
|
||||
if(auto srcArt = srcStack->getArt(ArtifactPosition::CREATURE_SLOT))
|
||||
if(srcStack->getArt(ArtifactPosition::CREATURE_SLOT))
|
||||
{
|
||||
if(auto dstArt = dstStack->getArt(ArtifactPosition::CREATURE_SLOT))
|
||||
{
|
||||
auto dstSlot = ArtifactUtils::getArtBackpackPosition(srcHero, dstArt->getTypeId());
|
||||
if(srcHero && dstSlot != ArtifactPosition::PRE_FIRST)
|
||||
{
|
||||
dstArt->move(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot);
|
||||
gs->map->moveArtifactInstance(*dstStack, ArtifactPosition::CREATURE_SLOT, *srcHero, dstSlot);
|
||||
}
|
||||
//else - artifact can be lost :/
|
||||
else
|
||||
@ -1610,12 +1609,12 @@ void RebalanceStacks::applyGs(CGameState *gs)
|
||||
ea.applyGs(gs);
|
||||
logNetwork->warn("Cannot move artifact! No free slots");
|
||||
}
|
||||
srcArt->move(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
|
||||
gs->map->moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
|
||||
//TODO: choose from dialog
|
||||
}
|
||||
else //just move to the other slot before stack gets erased
|
||||
{
|
||||
srcArt->move(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
|
||||
gs->map->moveArtifactInstance(*srcStack, ArtifactPosition::CREATURE_SLOT, *dstStack, ArtifactPosition::CREATURE_SLOT);
|
||||
}
|
||||
}
|
||||
if (stackExp)
|
||||
@ -1685,14 +1684,13 @@ void BulkSmartRebalanceStacks::applyGs(CGameState *gs)
|
||||
|
||||
void PutArtifact::applyGs(CGameState *gs)
|
||||
{
|
||||
// Ensure that artifact has been correctly added via NewArtifact pack
|
||||
assert(vstd::contains(gs->map->artInstances, art));
|
||||
auto art = gs->getArtInstance(id);
|
||||
assert(!art->getParentNodes().empty());
|
||||
auto hero = gs->getHero(al.artHolder);
|
||||
assert(hero);
|
||||
assert(art && art->canBePutAt(hero, al.slot));
|
||||
assert(ArtifactUtils::checkIfSlotValid(*hero, al.slot));
|
||||
art->putAt(*hero, al.slot);
|
||||
gs->map->putArtifactInstance(*hero, art, al.slot);
|
||||
}
|
||||
|
||||
void BulkEraseArtifacts::applyGs(CGameState *gs)
|
||||
@ -1731,15 +1729,13 @@ void BulkEraseArtifacts::applyGs(CGameState *gs)
|
||||
{
|
||||
logGlobal->debug("Erasing artifact %s", slotInfo->artifact->artType->getNameTranslated());
|
||||
}
|
||||
auto art = artSet->getArt(slot);
|
||||
assert(art);
|
||||
art->removeFrom(*artSet, slot);
|
||||
gs->map->removeArtifactInstance(*artSet, slot);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkMoveArtifacts::applyGs(CGameState *gs)
|
||||
{
|
||||
const auto bulkArtsRemove = [](std::vector<LinkedSlots> & artsPack, CArtifactSet & artSet)
|
||||
const auto bulkArtsRemove = [gs](std::vector<LinkedSlots> & artsPack, CArtifactSet & artSet)
|
||||
{
|
||||
std::vector<ArtifactPosition> packToRemove;
|
||||
for(const auto & slotsPair : artsPack)
|
||||
@ -1750,20 +1746,16 @@ void BulkMoveArtifacts::applyGs(CGameState *gs)
|
||||
});
|
||||
|
||||
for(const auto & slot : packToRemove)
|
||||
{
|
||||
auto * art = artSet.getArt(slot);
|
||||
assert(art);
|
||||
art->removeFrom(artSet, slot);
|
||||
}
|
||||
gs->map->removeArtifactInstance(artSet, slot);
|
||||
};
|
||||
|
||||
const auto bulkArtsPut = [](std::vector<LinkedSlots> & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet)
|
||||
const auto bulkArtsPut = [gs](std::vector<LinkedSlots> & artsPack, CArtifactSet & initArtSet, CArtifactSet & dstArtSet)
|
||||
{
|
||||
for(const auto & slotsPair : artsPack)
|
||||
{
|
||||
auto * art = initArtSet.getArt(slotsPair.srcPos);
|
||||
assert(art);
|
||||
art->putAt(dstArtSet, slotsPair.dstPos);
|
||||
gs->map->putArtifactInstance(dstArtSet, art, slotsPair.dstPos);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1840,7 +1832,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
|
||||
for(const auto slot : slotsInvolved)
|
||||
{
|
||||
const auto constituentInstance = hero->getArt(slot);
|
||||
constituentInstance->removeFrom(*hero, slot);
|
||||
gs->map->removeArtifactInstance(*hero, slot);
|
||||
|
||||
if(ArtifactUtils::isSlotEquipment(al.slot) && slot != al.slot)
|
||||
combinedArt->addPart(constituentInstance, slot);
|
||||
@ -1849,7 +1841,7 @@ void AssembledArtifact::applyGs(CGameState *gs)
|
||||
}
|
||||
|
||||
// Put new combined artifacts
|
||||
combinedArt->putAt(*hero, al.slot);
|
||||
gs->map->putArtifactInstance(*hero, combinedArt, al.slot);
|
||||
}
|
||||
|
||||
void DisassembledArtifact::applyGs(CGameState *gs)
|
||||
@ -1859,14 +1851,14 @@ void DisassembledArtifact::applyGs(CGameState *gs)
|
||||
auto disassembledArt = hero->getArt(al.slot);
|
||||
assert(disassembledArt);
|
||||
|
||||
auto parts = disassembledArt->getPartsInfo();
|
||||
disassembledArt->removeFrom(*hero, al.slot);
|
||||
const auto parts = disassembledArt->getPartsInfo();
|
||||
gs->map->removeArtifactInstance(*hero, al.slot);
|
||||
for(auto & part : parts)
|
||||
{
|
||||
// ArtifactPosition::PRE_FIRST is value of main part slot -> it'll replace combined artifact in its pos
|
||||
auto slot = (ArtifactUtils::isSlotEquipment(part.slot) ? part.slot : al.slot);
|
||||
disassembledArt->detachFrom(*part.art);
|
||||
part.art->putAt(*hero, slot);
|
||||
gs->map->putArtifactInstance(*hero, part.art, slot);
|
||||
}
|
||||
gs->map->eraseArtifactInstance(disassembledArt);
|
||||
}
|
||||
|
@ -965,14 +965,14 @@ struct DLL_LINKAGE CArtifactOperationPack : CPackForClient
|
||||
struct DLL_LINKAGE PutArtifact : CArtifactOperationPack
|
||||
{
|
||||
PutArtifact() = default;
|
||||
explicit PutArtifact(const ArtifactLocation & dst, bool askAssemble = true)
|
||||
: al(dst), askAssemble(askAssemble)
|
||||
explicit PutArtifact(const ArtifactInstanceID & id, const ArtifactLocation & dst, bool askAssemble = true)
|
||||
: al(dst), askAssemble(askAssemble), id(id)
|
||||
{
|
||||
}
|
||||
|
||||
ArtifactLocation al;
|
||||
bool askAssemble;
|
||||
ConstTransitivePtr<CArtifactInstance> art;
|
||||
ArtifactInstanceID id;
|
||||
|
||||
void applyGs(CGameState * gs) override;
|
||||
void visitTyped(ICPackVisitor & visitor) override;
|
||||
@ -981,7 +981,7 @@ struct DLL_LINKAGE PutArtifact : CArtifactOperationPack
|
||||
{
|
||||
h & al;
|
||||
h & askAssemble;
|
||||
h & art;
|
||||
h & id;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -526,6 +526,11 @@ bool Rewardable::Info::givesBonuses() const
|
||||
return testForKey(parameters, "bonuses");
|
||||
}
|
||||
|
||||
bool Rewardable::Info::hasGuards() const
|
||||
{
|
||||
return testForKey(parameters, "guards");
|
||||
}
|
||||
|
||||
const JsonNode & Rewardable::Info::getParameters() const
|
||||
{
|
||||
return parameters;
|
||||
|
@ -68,6 +68,8 @@ public:
|
||||
|
||||
bool givesBonuses() const override;
|
||||
|
||||
bool hasGuards() const override;
|
||||
|
||||
void configureObject(Rewardable::Configuration & object, vstd::RNG & rng, IGameCallback * cb) const;
|
||||
|
||||
void init(const JsonNode & objectConfig, const std::string & objectTextID);
|
||||
|
@ -25,6 +25,8 @@
|
||||
#include "../networkPacks/PacksForClient.h"
|
||||
#include "../IGameCallback.h"
|
||||
|
||||
#include <vstd/RNG.h>
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
std::vector<ui32> Rewardable::Interface::getAvailableRewards(const CGHeroInstance * hero, Rewardable::EEventType event) const
|
||||
@ -44,8 +46,10 @@ std::vector<ui32> Rewardable::Interface::getAvailableRewards(const CGHeroInstanc
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const
|
||||
void Rewardable::Interface::grantRewardBeforeLevelup(const Rewardable::VisitInfo & info, const CGHeroInstance * hero) const
|
||||
{
|
||||
auto cb = getObject()->cb;
|
||||
|
||||
assert(hero);
|
||||
assert(hero->tempOwner.isValidPlayer());
|
||||
assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE);
|
||||
@ -129,8 +133,10 @@ void Rewardable::Interface::grantRewardBeforeLevelup(IGameCallback * cb, const R
|
||||
cb->giveExperience(hero, expToGive);
|
||||
}
|
||||
|
||||
void Rewardable::Interface::grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const
|
||||
void Rewardable::Interface::grantRewardAfterLevelup(const Rewardable::VisitInfo & info, const CArmedInstance * army, const CGHeroInstance * hero) const
|
||||
{
|
||||
auto cb = getObject()->cb;
|
||||
|
||||
if(info.reward.manaDiff || info.reward.manaPercentage >= 0)
|
||||
cb->setManaPoints(hero->id, info.reward.calculateManaPoints(hero));
|
||||
|
||||
@ -216,4 +222,148 @@ void Rewardable::Interface::serializeJson(JsonSerializeFormat & handler)
|
||||
configuration.serializeJson(handler);
|
||||
}
|
||||
|
||||
void Rewardable::Interface::grantRewardWithMessage(const CGHeroInstance * contextHero, int index, bool markAsVisit) const
|
||||
{
|
||||
auto vi = configuration.info.at(index);
|
||||
logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
|
||||
// show message only if it is not empty or in infobox
|
||||
if (configuration.infoWindowType != EInfoWindowMode::MODAL || !vi.message.toString().empty())
|
||||
{
|
||||
InfoWindow iw;
|
||||
iw.player = contextHero->tempOwner;
|
||||
iw.text = vi.message;
|
||||
vi.reward.loadComponents(iw.components, contextHero);
|
||||
iw.type = configuration.infoWindowType;
|
||||
if(!iw.components.empty() || !iw.text.toString().empty())
|
||||
getObject()->cb->showInfoDialog(&iw);
|
||||
}
|
||||
// grant reward afterwards. Note that it may remove object
|
||||
if(markAsVisit)
|
||||
markAsVisited(contextHero);
|
||||
grantReward(index, contextHero);
|
||||
}
|
||||
|
||||
void Rewardable::Interface::selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const
|
||||
{
|
||||
BlockingDialog sd(configuration.canRefuse, rewardIndices.size() > 1);
|
||||
sd.player = contextHero->tempOwner;
|
||||
sd.text = dialog;
|
||||
sd.components = loadComponents(contextHero, rewardIndices);
|
||||
getObject()->cb->showBlockingDialog(getObject(), &sd);
|
||||
}
|
||||
|
||||
std::vector<Component> Rewardable::Interface::loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const
|
||||
{
|
||||
std::vector<Component> result;
|
||||
|
||||
if (rewardIndices.empty())
|
||||
return result;
|
||||
|
||||
if (configuration.selectMode != Rewardable::SELECT_FIRST && rewardIndices.size() > 1)
|
||||
{
|
||||
for (auto index : rewardIndices)
|
||||
result.push_back(configuration.info.at(index).reward.getDisplayedComponent(contextHero));
|
||||
}
|
||||
else
|
||||
{
|
||||
configuration.info.at(rewardIndices.front()).reward.loadComponents(result, contextHero);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Rewardable::Interface::grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, bool markAsVisit) const
|
||||
{
|
||||
if (rewardIndices.empty())
|
||||
return;
|
||||
|
||||
for (auto index : rewardIndices)
|
||||
{
|
||||
// TODO: Merge all rewards of same type, with single message?
|
||||
grantRewardWithMessage(contextHero, index, false);
|
||||
}
|
||||
// Mark visited only after all rewards were processed
|
||||
if(markAsVisit)
|
||||
markAsVisited(contextHero);
|
||||
}
|
||||
|
||||
void Rewardable::Interface::doHeroVisit(const CGHeroInstance *h) const
|
||||
{
|
||||
if(!wasVisitedBefore(h))
|
||||
{
|
||||
auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
|
||||
bool objectRemovalPossible = false;
|
||||
for(auto index : rewards)
|
||||
{
|
||||
if(configuration.info.at(index).reward.removeObject)
|
||||
objectRemovalPossible = true;
|
||||
}
|
||||
|
||||
logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
|
||||
switch (rewards.size())
|
||||
{
|
||||
case 0: // no available rewards, e.g. visiting School of War without gold
|
||||
{
|
||||
auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
|
||||
if (!emptyRewards.empty())
|
||||
grantRewardWithMessage(h, emptyRewards[0], false);
|
||||
else
|
||||
logMod->warn("No applicable message for visiting empty object!");
|
||||
break;
|
||||
}
|
||||
case 1: // one reward. Just give it with message
|
||||
{
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, rewards, configuration.info.at(rewards.front()).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewards.front(), true);
|
||||
break;
|
||||
}
|
||||
default: // multiple rewards. Act according to select mode
|
||||
{
|
||||
switch (configuration.selectMode) {
|
||||
case Rewardable::SELECT_PLAYER: // player must select
|
||||
selectRewardWithMessage(h, rewards, configuration.onSelect);
|
||||
break;
|
||||
case Rewardable::SELECT_FIRST: // give first available
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, { rewards.front() }, configuration.info.at(rewards.front()).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewards.front(), true);
|
||||
break;
|
||||
case Rewardable::SELECT_RANDOM: // give random
|
||||
{
|
||||
ui32 rewardIndex = *RandomGeneratorUtil::nextItem(rewards, getObject()->cb->getRandomGenerator());
|
||||
if (configuration.canRefuse)
|
||||
selectRewardWithMessage(h, { rewardIndex }, configuration.info.at(rewardIndex).message);
|
||||
else
|
||||
grantRewardWithMessage(h, rewardIndex, true);
|
||||
break;
|
||||
}
|
||||
case Rewardable::SELECT_ALL: // grant all possible
|
||||
grantAllRewardsWithMessage(h, rewards, true);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!objectRemovalPossible && getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT).empty())
|
||||
markAsScouted(h);
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->debug("Revisiting already visited object");
|
||||
|
||||
if (!wasVisited(h->getOwner()))
|
||||
markAsScouted(h);
|
||||
|
||||
auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
|
||||
if (!visitedRewards.empty())
|
||||
grantRewardWithMessage(h, visitedRewards[0], false);
|
||||
else
|
||||
logMod->warn("No applicable message for visiting already visited object!");
|
||||
}
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -15,7 +15,7 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class IGameCallback;
|
||||
class IObjectInterface;
|
||||
|
||||
namespace Rewardable
|
||||
{
|
||||
@ -30,11 +30,24 @@ private:
|
||||
protected:
|
||||
|
||||
/// function that must be called if hero got level-up during grantReward call
|
||||
virtual void grantRewardAfterLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const;
|
||||
void grantRewardAfterLevelup(const Rewardable::VisitInfo & reward, const CArmedInstance * army, const CGHeroInstance * hero) const;
|
||||
|
||||
/// grants reward to hero
|
||||
virtual void grantRewardBeforeLevelup(IGameCallback * cb, const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const;
|
||||
void grantRewardBeforeLevelup(const Rewardable::VisitInfo & reward, const CGHeroInstance * hero) const;
|
||||
|
||||
virtual void grantRewardWithMessage(const CGHeroInstance * contextHero, int rewardIndex, bool markAsVisit) const;
|
||||
void selectRewardWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices, const MetaString & dialog) const;
|
||||
void grantAllRewardsWithMessage(const CGHeroInstance * contextHero, const std::vector<ui32>& rewardIndices, bool markAsVisit) const;
|
||||
std::vector<Component> loadComponents(const CGHeroInstance * contextHero, const std::vector<ui32> & rewardIndices) const;
|
||||
|
||||
void doHeroVisit(const CGHeroInstance *h) const;
|
||||
|
||||
virtual const IObjectInterface * getObject() const = 0;
|
||||
virtual bool wasVisitedBefore(const CGHeroInstance * hero) const = 0;
|
||||
virtual bool wasVisited(PlayerColor player) const = 0;
|
||||
virtual void markAsVisited(const CGHeroInstance * hero) const = 0;
|
||||
virtual void markAsScouted(const CGHeroInstance * hero) const = 0;
|
||||
virtual void grantReward(ui32 rewardID, const CGHeroInstance * hero) const = 0;
|
||||
public:
|
||||
|
||||
/// filters list of visit info and returns rewards that can be granted to current hero
|
||||
|
@ -143,10 +143,28 @@ bool Rewardable::Limiter::heroAllowed(const CGHeroInstance * hero) const
|
||||
for(const auto & elem : artifactsRequirements)
|
||||
{
|
||||
// check required amount of artifacts
|
||||
if(hero->getArtPosCount(elem.first, false, true, true) < elem.second)
|
||||
size_t artCnt = 0;
|
||||
for(const auto & [slot, slotInfo] : hero->artifactsWorn)
|
||||
if(slotInfo.artifact->getTypeId() == elem.first)
|
||||
artCnt++;
|
||||
|
||||
for(auto & slotInfo : hero->artifactsInBackpack)
|
||||
if(slotInfo.artifact->getTypeId() == elem.first)
|
||||
{
|
||||
artCnt++;
|
||||
}
|
||||
else if(slotInfo.artifact->isCombined())
|
||||
{
|
||||
for(const auto & partInfo : slotInfo.artifact->getPartsInfo())
|
||||
if(partInfo.art->getTypeId() == elem.first)
|
||||
artCnt++;
|
||||
}
|
||||
|
||||
if(artCnt < elem.second)
|
||||
return false;
|
||||
if(!hero->hasArt(elem.first))
|
||||
reqSlots += hero->getAssemblyByConstituent(elem.first)->getPartsInfo().size() - 2;
|
||||
// Check if art has no own slot. (As part of combined in backpack)
|
||||
if(hero->getArtPos(elem.first, false) == ArtifactPosition::PRE_FIRST)
|
||||
reqSlots += hero->getCombinedArtWithPart(elem.first)->getPartsInfo().size() - 2;
|
||||
}
|
||||
if(!ArtifactUtils::isBackpackFreeSlots(hero, reqSlots))
|
||||
return false;
|
||||
|
@ -102,6 +102,7 @@ void ZoneOptions::CTownInfo::serializeJson(JsonSerializeFormat & handler)
|
||||
handler.serializeInt("castles", castleCount, 0);
|
||||
handler.serializeInt("townDensity", townDensity, 0);
|
||||
handler.serializeInt("castleDensity", castleDensity, 0);
|
||||
handler.serializeInt("sourceZone", sourceZone, NO_ZONE);
|
||||
}
|
||||
|
||||
ZoneOptions::ZoneOptions():
|
||||
@ -156,7 +157,7 @@ std::optional<int> ZoneOptions::getOwner() const
|
||||
return owner;
|
||||
}
|
||||
|
||||
const std::set<TerrainId> ZoneOptions::getTerrainTypes() const
|
||||
std::set<TerrainId> ZoneOptions::getTerrainTypes() const
|
||||
{
|
||||
if (terrainTypes.empty())
|
||||
{
|
||||
@ -191,7 +192,7 @@ std::set<FactionID> ZoneOptions::getDefaultTownTypes() const
|
||||
return VLC->townh->getDefaultAllowed();
|
||||
}
|
||||
|
||||
const std::set<FactionID> ZoneOptions::getTownTypes() const
|
||||
std::set<FactionID> ZoneOptions::getTownTypes() const
|
||||
{
|
||||
if (townTypes.empty())
|
||||
{
|
||||
@ -214,7 +215,7 @@ void ZoneOptions::setMonsterTypes(const std::set<FactionID> & value)
|
||||
monsterTypes = value;
|
||||
}
|
||||
|
||||
const std::set<FactionID> ZoneOptions::getMonsterTypes() const
|
||||
std::set<FactionID> ZoneOptions::getMonsterTypes() const
|
||||
{
|
||||
return vstd::difference(monsterTypes, bannedMonsters);
|
||||
}
|
||||
@ -250,7 +251,7 @@ void ZoneOptions::addTreasureInfo(const CTreasureInfo & value)
|
||||
vstd::amax(maxTreasureValue, value.max);
|
||||
}
|
||||
|
||||
const std::vector<CTreasureInfo> & ZoneOptions::getTreasureInfo() const
|
||||
std::vector<CTreasureInfo> ZoneOptions::getTreasureInfo() const
|
||||
{
|
||||
return treasureInfo;
|
||||
}
|
||||
@ -272,7 +273,22 @@ TRmgTemplateZoneId ZoneOptions::getTerrainTypeLikeZone() const
|
||||
|
||||
TRmgTemplateZoneId ZoneOptions::getTreasureLikeZone() const
|
||||
{
|
||||
return treasureLikeZone;
|
||||
return treasureLikeZone;
|
||||
}
|
||||
|
||||
ObjectConfig ZoneOptions::getCustomObjects() const
|
||||
{
|
||||
return objectConfig;
|
||||
}
|
||||
|
||||
void ZoneOptions::setCustomObjects(const ObjectConfig & value)
|
||||
{
|
||||
objectConfig = value;
|
||||
}
|
||||
|
||||
TRmgTemplateZoneId ZoneOptions::getCustomObjectsLikeZone() const
|
||||
{
|
||||
return customObjectsLikeZone;
|
||||
}
|
||||
|
||||
void ZoneOptions::addConnection(const ZoneConnection & connection)
|
||||
@ -334,6 +350,7 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
|
||||
SERIALIZE_ZONE_LINK(minesLikeZone);
|
||||
SERIALIZE_ZONE_LINK(terrainTypeLikeZone);
|
||||
SERIALIZE_ZONE_LINK(treasureLikeZone);
|
||||
SERIALIZE_ZONE_LINK(customObjectsLikeZone);
|
||||
|
||||
#undef SERIALIZE_ZONE_LINK
|
||||
|
||||
@ -398,6 +415,8 @@ void ZoneOptions::serializeJson(JsonSerializeFormat & handler)
|
||||
handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], mines[idx], 0);
|
||||
}
|
||||
}
|
||||
|
||||
handler.serializeStruct("customObjects", objectConfig);
|
||||
}
|
||||
|
||||
ZoneConnection::ZoneConnection():
|
||||
@ -759,53 +778,29 @@ const JsonNode & CRmgTemplate::getMapSettings() const
|
||||
return *mapSettings;
|
||||
}
|
||||
|
||||
std::set<TerrainId> CRmgTemplate::inheritTerrainType(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
template<typename T>
|
||||
T CRmgTemplate::inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,
|
||||
T (rmg::ZoneOptions::*getter)() const,
|
||||
void (rmg::ZoneOptions::*setter)(const T&),
|
||||
TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const,
|
||||
const std::string& propertyString,
|
||||
uint32_t iteration)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for terrain types detected in template %s", name);
|
||||
return std::set<TerrainId>();
|
||||
logGlobal->error("Infinite recursion for %s detected in template %s", propertyString, name);
|
||||
return T();
|
||||
}
|
||||
if (zone->getTerrainTypeLikeZone() != ZoneOptions::NO_ZONE)
|
||||
|
||||
if (((*zone).*inheritFrom)() != rmg::ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getTerrainTypeLikeZone());
|
||||
zone->setTerrainTypes(inheritTerrainType(otherZone, iteration));
|
||||
const auto otherZone = zones.at(((*zone).*inheritFrom)());
|
||||
T inheritedValue = inheritZoneProperty(otherZone, getter, setter, inheritFrom, propertyString, iteration);
|
||||
((*zone).*setter)(inheritedValue);
|
||||
}
|
||||
//This implicitly excludes banned terrains
|
||||
return zone->getTerrainTypes();
|
||||
}
|
||||
|
||||
std::map<TResource, ui16> CRmgTemplate::inheritMineTypes(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for mine types detected in template %s", name);
|
||||
return std::map<TResource, ui16>();
|
||||
}
|
||||
if (zone->getMinesLikeZone() != ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getMinesLikeZone());
|
||||
zone->setMinesInfo(inheritMineTypes(otherZone, iteration));
|
||||
}
|
||||
return zone->getMinesInfo();
|
||||
}
|
||||
|
||||
std::vector<CTreasureInfo> CRmgTemplate::inheritTreasureInfo(std::shared_ptr<ZoneOptions> zone, uint32_t iteration /* = 0 */)
|
||||
{
|
||||
if (iteration >= 50)
|
||||
{
|
||||
logGlobal->error("Infinite recursion for treasures detected in template %s", name);
|
||||
return std::vector<CTreasureInfo>();
|
||||
}
|
||||
if (zone->getTreasureLikeZone() != ZoneOptions::NO_ZONE)
|
||||
{
|
||||
iteration++;
|
||||
const auto otherZone = zones.at(zone->getTreasureLikeZone());
|
||||
zone->setTreasureInfo(inheritTreasureInfo(otherZone, iteration));
|
||||
}
|
||||
return zone->getTreasureInfo();
|
||||
|
||||
return ((*zone).*getter)();
|
||||
}
|
||||
|
||||
void CRmgTemplate::afterLoad()
|
||||
@ -814,12 +809,32 @@ void CRmgTemplate::afterLoad()
|
||||
{
|
||||
auto zone = idAndZone.second;
|
||||
|
||||
//Inherit properties recursively.
|
||||
inheritTerrainType(zone);
|
||||
inheritMineTypes(zone);
|
||||
inheritTreasureInfo(zone);
|
||||
// Inherit properties recursively
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getTerrainTypes,
|
||||
&rmg::ZoneOptions::setTerrainTypes,
|
||||
&rmg::ZoneOptions::getTerrainTypeLikeZone,
|
||||
"terrain types");
|
||||
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getMinesInfo,
|
||||
&rmg::ZoneOptions::setMinesInfo,
|
||||
&rmg::ZoneOptions::getMinesLikeZone,
|
||||
"mine types");
|
||||
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getTreasureInfo,
|
||||
&rmg::ZoneOptions::setTreasureInfo,
|
||||
&rmg::ZoneOptions::getTreasureLikeZone,
|
||||
"treasure info");
|
||||
|
||||
//TODO: Inherit monster types as well
|
||||
inheritZoneProperty(zone,
|
||||
&rmg::ZoneOptions::getCustomObjects,
|
||||
&rmg::ZoneOptions::setCustomObjects,
|
||||
&rmg::ZoneOptions::getCustomObjectsLikeZone,
|
||||
"custom objects");
|
||||
|
||||
//TODO: Inherit monster types as well
|
||||
auto monsterTypes = zone->getMonsterTypes();
|
||||
if (monsterTypes.empty())
|
||||
{
|
||||
@ -848,6 +863,7 @@ void CRmgTemplate::afterLoad()
|
||||
allowedWaterContent.erase(EWaterContent::RANDOM);
|
||||
}
|
||||
|
||||
// TODO: Allow any integer size which does not match enum, as well
|
||||
void CRmgTemplate::serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName)
|
||||
{
|
||||
static const std::map<std::string, int3> sizeMapping =
|
||||
@ -916,5 +932,19 @@ void CRmgTemplate::serializePlayers(JsonSerializeFormat & handler, CPlayerCountR
|
||||
value.fromString(encodedValue);
|
||||
}
|
||||
|
||||
const std::vector<CompoundMapObjectID> & ZoneOptions::getBannedObjects() const
|
||||
{
|
||||
return objectConfig.getBannedObjects();
|
||||
}
|
||||
|
||||
const std::vector<ObjectConfig::EObjectCategory> & ZoneOptions::getBannedObjectCategories() const
|
||||
{
|
||||
return objectConfig.getBannedObjectCategories();
|
||||
}
|
||||
|
||||
const std::vector<ObjectInfo> & ZoneOptions::getConfiguredObjects() const
|
||||
{
|
||||
return objectConfig.getConfiguredObjects();
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -13,10 +13,14 @@
|
||||
#include "../int3.h"
|
||||
#include "../GameConstants.h"
|
||||
#include "../ResourceSet.h"
|
||||
#include "ObjectInfo.h"
|
||||
#include "ObjectConfig.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class JsonSerializeFormat;
|
||||
struct CompoundMapObjectID;
|
||||
|
||||
enum class ETemplateZoneType
|
||||
{
|
||||
@ -132,6 +136,9 @@ public:
|
||||
int castleCount;
|
||||
int townDensity;
|
||||
int castleDensity;
|
||||
|
||||
// TODO: Copy from another zone once its randomized
|
||||
TRmgTemplateZoneId sourceZone = NO_ZONE;
|
||||
};
|
||||
|
||||
ZoneOptions();
|
||||
@ -146,15 +153,15 @@ public:
|
||||
void setSize(int value);
|
||||
std::optional<int> getOwner() const;
|
||||
|
||||
const std::set<TerrainId> getTerrainTypes() const;
|
||||
std::set<TerrainId> getTerrainTypes() const;
|
||||
void setTerrainTypes(const std::set<TerrainId> & value);
|
||||
std::set<TerrainId> getDefaultTerrainTypes() const;
|
||||
|
||||
const CTownInfo & getPlayerTowns() const;
|
||||
const CTownInfo & getNeutralTowns() const;
|
||||
std::set<FactionID> getDefaultTownTypes() const;
|
||||
const std::set<FactionID> getTownTypes() const;
|
||||
const std::set<FactionID> getMonsterTypes() const;
|
||||
std::set<FactionID> getTownTypes() const;
|
||||
std::set<FactionID> getMonsterTypes() const;
|
||||
|
||||
void setTownTypes(const std::set<FactionID> & value);
|
||||
void setMonsterTypes(const std::set<FactionID> & value);
|
||||
@ -164,7 +171,7 @@ public:
|
||||
|
||||
void setTreasureInfo(const std::vector<CTreasureInfo> & value);
|
||||
void addTreasureInfo(const CTreasureInfo & value);
|
||||
const std::vector<CTreasureInfo> & getTreasureInfo() const;
|
||||
std::vector<CTreasureInfo> getTreasureInfo() const;
|
||||
ui32 getMaxTreasureValue() const;
|
||||
void recalculateMaxTreasureValue();
|
||||
|
||||
@ -183,12 +190,24 @@ public:
|
||||
bool areTownsSameType() const;
|
||||
bool isMatchTerrainToTown() const;
|
||||
|
||||
// Get a group of configured objects
|
||||
const std::vector<CompoundMapObjectID> & getBannedObjects() const;
|
||||
const std::vector<ObjectConfig::EObjectCategory> & getBannedObjectCategories() const;
|
||||
const std::vector<ObjectInfo> & getConfiguredObjects() const;
|
||||
|
||||
// Copy whole custom object config from another zone
|
||||
ObjectConfig getCustomObjects() const;
|
||||
void setCustomObjects(const ObjectConfig & value);
|
||||
TRmgTemplateZoneId getCustomObjectsLikeZone() const;
|
||||
|
||||
protected:
|
||||
TRmgTemplateZoneId id;
|
||||
ETemplateZoneType type;
|
||||
int size;
|
||||
ui32 maxTreasureValue;
|
||||
std::optional<int> owner;
|
||||
|
||||
ObjectConfig objectConfig;
|
||||
CTownInfo playerTowns;
|
||||
CTownInfo neutralTowns;
|
||||
bool matchTerrainToTown;
|
||||
@ -211,6 +230,7 @@ protected:
|
||||
TRmgTemplateZoneId minesLikeZone;
|
||||
TRmgTemplateZoneId terrainTypeLikeZone;
|
||||
TRmgTemplateZoneId treasureLikeZone;
|
||||
TRmgTemplateZoneId customObjectsLikeZone;
|
||||
};
|
||||
|
||||
}
|
||||
@ -280,8 +300,21 @@ private:
|
||||
std::set<TerrainId> inheritTerrainType(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
std::map<TResource, ui16> inheritMineTypes(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
std::vector<CTreasureInfo> inheritTreasureInfo(std::shared_ptr<rmg::ZoneOptions> zone, uint32_t iteration = 0);
|
||||
|
||||
// TODO: Copy custom object settings
|
||||
// TODO: Copy town type after source town is actually randomized
|
||||
|
||||
void serializeSize(JsonSerializeFormat & handler, int3 & value, const std::string & fieldName);
|
||||
void serializePlayers(JsonSerializeFormat & handler, CPlayerCountRange & value, const std::string & fieldName);
|
||||
|
||||
template<typename T>
|
||||
T inheritZoneProperty(std::shared_ptr<rmg::ZoneOptions> zone,
|
||||
T (rmg::ZoneOptions::*getter)() const,
|
||||
void (rmg::ZoneOptions::*setter)(const T&),
|
||||
TRmgTemplateZoneId (rmg::ZoneOptions::*inheritFrom)() const,
|
||||
const std::string& propertyString,
|
||||
uint32_t iteration = 0);
|
||||
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
VCMI_LIB_NAMESPACE_END
|
189
lib/rmg/ObjectConfig.cpp
Normal file
189
lib/rmg/ObjectConfig.cpp
Normal file
@ -0,0 +1,189 @@
|
||||
/*
|
||||
* ObjectConfig.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include <boost/bimap.hpp>
|
||||
#include <boost/assign.hpp>
|
||||
#include "ObjectInfo.h"
|
||||
#include "ObjectConfig.h"
|
||||
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
void ObjectConfig::addBannedObject(const CompoundMapObjectID & objid)
|
||||
{
|
||||
// FIXME: We do not need to store the object info, just the id
|
||||
|
||||
bannedObjects.push_back(objid);
|
||||
|
||||
logGlobal->info("Banned object of type %d.%d", objid.primaryID, objid.secondaryID);
|
||||
}
|
||||
|
||||
void ObjectConfig::addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid)
|
||||
{
|
||||
customObjects.push_back(object);
|
||||
auto & lastObject = customObjects.back();
|
||||
lastObject.setAllTemplates(objid.primaryID, objid.secondaryID);
|
||||
|
||||
assert(lastObject.templates.size() > 0);
|
||||
logGlobal->info("Added custom object of type %d.%d", objid.primaryID, objid.secondaryID);
|
||||
}
|
||||
|
||||
void ObjectConfig::serializeJson(JsonSerializeFormat & handler)
|
||||
{
|
||||
// TODO: We need serializer utility for list of enum values
|
||||
|
||||
static const boost::bimap<EObjectCategory, std::string> OBJECT_CATEGORY_STRINGS = boost::assign::list_of<boost::bimap<EObjectCategory, std::string>::relation>
|
||||
(EObjectCategory::OTHER, "other")
|
||||
(EObjectCategory::ALL, "all")
|
||||
(EObjectCategory::NONE, "none")
|
||||
(EObjectCategory::CREATURE_BANK, "creatureBank")
|
||||
(EObjectCategory::BONUS, "bonus")
|
||||
(EObjectCategory::DWELLING, "dwelling")
|
||||
(EObjectCategory::RESOURCE, "resource")
|
||||
(EObjectCategory::RESOURCE_GENERATOR, "resourceGenerator")
|
||||
(EObjectCategory::SPELL_SCROLL, "spellScroll")
|
||||
(EObjectCategory::RANDOM_ARTIFACT, "randomArtifact")
|
||||
(EObjectCategory::PANDORAS_BOX, "pandorasBox")
|
||||
(EObjectCategory::QUEST_ARTIFACT, "questArtifact")
|
||||
(EObjectCategory::SEER_HUT, "seerHut");
|
||||
|
||||
|
||||
// TODO: Separate into individual methods to enforce RAII destruction?
|
||||
{
|
||||
auto categories = handler.enterArray("bannedCategories");
|
||||
if (handler.saving)
|
||||
{
|
||||
for (const auto& category : bannedObjectCategories)
|
||||
{
|
||||
auto str = OBJECT_CATEGORY_STRINGS.left.at(category);
|
||||
categories.serializeString(categories.size(), str);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> categoryNames;
|
||||
categories.serializeArray(categoryNames);
|
||||
|
||||
for (const auto & categoryName : categoryNames)
|
||||
{
|
||||
auto it = OBJECT_CATEGORY_STRINGS.right.find(categoryName);
|
||||
if (it != OBJECT_CATEGORY_STRINGS.right.end())
|
||||
{
|
||||
bannedObjectCategories.push_back(it->second);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Doesn't seem to use this field at all
|
||||
|
||||
{
|
||||
auto bannedObjectData = handler.enterArray("bannedObjects");
|
||||
if (handler.saving)
|
||||
{
|
||||
|
||||
// FIXME: Do we even need to serialize / store banned objects?
|
||||
/*
|
||||
for (const auto & object : bannedObjects)
|
||||
{
|
||||
// TODO: Translate id back to string?
|
||||
|
||||
|
||||
JsonNode node;
|
||||
node.String() = VLC->objtypeh->getHandlerFor(object.primaryID, object.secondaryID);
|
||||
// TODO: Check if AI-generated code is right
|
||||
|
||||
|
||||
}
|
||||
// handler.serializeRaw("bannedObjects", node, std::nullopt);
|
||||
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::string> objectNames;
|
||||
bannedObjectData.serializeArray(objectNames);
|
||||
|
||||
for (const auto & objectName : objectNames)
|
||||
{
|
||||
VLC->objtypeh->resolveObjectCompoundId(objectName,
|
||||
[this](CompoundMapObjectID objid)
|
||||
{
|
||||
addBannedObject(objid);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto commonObjectData = handler.getCurrent()["commonObjects"].Vector();
|
||||
if (handler.saving)
|
||||
{
|
||||
|
||||
//TODO?
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto & objectConfig : commonObjectData)
|
||||
{
|
||||
auto objectName = objectConfig["id"].String();
|
||||
auto rmg = objectConfig["rmg"].Struct();
|
||||
|
||||
// TODO: Use common code with default rmg config
|
||||
auto objectValue = rmg["value"].Integer();
|
||||
auto objectProbability = rmg["rarity"].Integer();
|
||||
|
||||
auto objectMaxPerZone = rmg["zoneLimit"].Integer();
|
||||
if (objectMaxPerZone == 0)
|
||||
{
|
||||
objectMaxPerZone = std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
VLC->objtypeh->resolveObjectCompoundId(objectName,
|
||||
|
||||
[this, objectValue, objectProbability, objectMaxPerZone](CompoundMapObjectID objid)
|
||||
{
|
||||
ObjectInfo object(objid.primaryID, objid.secondaryID);
|
||||
|
||||
// TODO: Configure basic generateObject function
|
||||
|
||||
object.value = objectValue;
|
||||
object.probability = objectProbability;
|
||||
object.maxPerZone = objectMaxPerZone;
|
||||
addCustomObject(object, objid);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ObjectInfo> & ObjectConfig::getConfiguredObjects() const
|
||||
{
|
||||
return customObjects;
|
||||
}
|
||||
|
||||
const std::vector<CompoundMapObjectID> & ObjectConfig::getBannedObjects() const
|
||||
{
|
||||
return bannedObjects;
|
||||
}
|
||||
|
||||
const std::vector<ObjectConfig::EObjectCategory> & ObjectConfig::getBannedObjectCategories() const
|
||||
{
|
||||
return bannedObjectCategories;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
57
lib/rmg/ObjectConfig.h
Normal file
57
lib/rmg/ObjectConfig.h
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* ObjectInfo.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
class DLL_LINKAGE ObjectConfig
|
||||
{
|
||||
public:
|
||||
|
||||
enum class EObjectCategory
|
||||
{
|
||||
OTHER = -2,
|
||||
ALL = -1,
|
||||
NONE = 0,
|
||||
CREATURE_BANK = 1,
|
||||
BONUS,
|
||||
DWELLING,
|
||||
RESOURCE,
|
||||
RESOURCE_GENERATOR,
|
||||
SPELL_SCROLL,
|
||||
RANDOM_ARTIFACT,
|
||||
PANDORAS_BOX,
|
||||
QUEST_ARTIFACT,
|
||||
SEER_HUT
|
||||
};
|
||||
|
||||
void addBannedObject(const CompoundMapObjectID & objid);
|
||||
void addCustomObject(const ObjectInfo & object, const CompoundMapObjectID & objid);
|
||||
void clearBannedObjects();
|
||||
void clearCustomObjects();
|
||||
const std::vector<CompoundMapObjectID> & getBannedObjects() const;
|
||||
const std::vector<EObjectCategory> & getBannedObjectCategories() const;
|
||||
const std::vector<ObjectInfo> & getConfiguredObjects() const;
|
||||
|
||||
void serializeJson(JsonSerializeFormat & handler);
|
||||
private:
|
||||
// TODO: Add convenience method for banning objects by name
|
||||
std::vector<CompoundMapObjectID> bannedObjects;
|
||||
std::vector<EObjectCategory> bannedObjectCategories;
|
||||
|
||||
// TODO: In what format should I store custom objects?
|
||||
// Need to convert map serialization format to ObjectInfo
|
||||
std::vector<ObjectInfo> customObjects;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
85
lib/rmg/ObjectInfo.cpp
Normal file
85
lib/rmg/ObjectInfo.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* ObjectInfo.cpp, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "ObjectInfo.h"
|
||||
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../serializer/JsonSerializeFormat.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ObjectInfo::ObjectInfo(si32 ID, si32 subID):
|
||||
primaryID(ID),
|
||||
secondaryID(subID),
|
||||
destroyObject([](CGObjectInstance * obj){}),
|
||||
maxPerZone(std::numeric_limits<ui32>::max())
|
||||
{
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo(CompoundMapObjectID id):
|
||||
ObjectInfo(id.primaryID, id.secondaryID)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectInfo::ObjectInfo(const ObjectInfo & other)
|
||||
{
|
||||
templates = other.templates;
|
||||
primaryID = other.primaryID;
|
||||
secondaryID = other.secondaryID;
|
||||
value = other.value;
|
||||
probability = other.probability;
|
||||
maxPerZone = other.maxPerZone;
|
||||
generateObject = other.generateObject;
|
||||
destroyObject = other.destroyObject;
|
||||
}
|
||||
|
||||
ObjectInfo & ObjectInfo::operator=(const ObjectInfo & other)
|
||||
{
|
||||
if (this == &other)
|
||||
return *this;
|
||||
|
||||
templates = other.templates;
|
||||
primaryID = other.primaryID;
|
||||
secondaryID = other.secondaryID;
|
||||
value = other.value;
|
||||
probability = other.probability;
|
||||
maxPerZone = other.maxPerZone;
|
||||
generateObject = other.generateObject;
|
||||
destroyObject = other.destroyObject;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ObjectInfo::setAllTemplates(MapObjectID type, MapObjectSubID subtype)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates();
|
||||
}
|
||||
|
||||
void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates(terrainType);
|
||||
}
|
||||
|
||||
CompoundMapObjectID ObjectInfo::getCompoundID() const
|
||||
{
|
||||
return CompoundMapObjectID(primaryID, secondaryID);
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
45
lib/rmg/ObjectInfo.h
Normal file
45
lib/rmg/ObjectInfo.h
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* ObjectInfo.h, part of VCMI engine
|
||||
*
|
||||
* Authors: listed in file AUTHORS in main folder
|
||||
*
|
||||
* License: GNU General Public License v2.0 or later
|
||||
* Full text of license available in license.txt file, in main folder
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../mapObjects/ObjectTemplate.h"
|
||||
#include "../mapObjects/CompoundMapObjectID.h"
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
struct CompoundMapObjectID;
|
||||
class CGObjectInstance;
|
||||
|
||||
struct DLL_LINKAGE ObjectInfo
|
||||
{
|
||||
ObjectInfo(si32 ID, si32 subID);
|
||||
ObjectInfo(CompoundMapObjectID id);
|
||||
ObjectInfo(const ObjectInfo & other);
|
||||
ObjectInfo & operator=(const ObjectInfo & other);
|
||||
|
||||
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
|
||||
si32 primaryID;
|
||||
si32 secondaryID;
|
||||
ui32 value = 0;
|
||||
ui16 probability = 0;
|
||||
ui32 maxPerZone = 1;
|
||||
//ui32 maxPerMap; //unused
|
||||
std::function<CGObjectInstance *()> generateObject;
|
||||
std::function<void(CGObjectInstance *)> destroyObject;
|
||||
|
||||
void setAllTemplates(MapObjectID type, MapObjectSubID subtype);
|
||||
void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
|
||||
|
||||
CompoundMapObjectID getCompoundID() const;
|
||||
//bool matchesId(const CompoundMapObjectID & id) const;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
@ -45,7 +45,6 @@ void ObjectDistributor::init()
|
||||
|
||||
void ObjectDistributor::distributeLimitedObjects()
|
||||
{
|
||||
ObjectInfo oi;
|
||||
auto zones = map.getZones();
|
||||
|
||||
for (auto primaryID : VLC->objtypeh->knownObjects())
|
||||
@ -81,6 +80,8 @@ void ObjectDistributor::distributeLimitedObjects()
|
||||
RandomGeneratorUtil::randomShuffle(matchingZones, zone.getRand());
|
||||
for (auto& zone : matchingZones)
|
||||
{
|
||||
ObjectInfo oi(primaryID, secondaryID);
|
||||
|
||||
oi.generateObject = [cb=map.mapInstance->cb, primaryID, secondaryID]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(cb, nullptr);
|
||||
|
@ -10,6 +10,7 @@
|
||||
|
||||
#include "StdInc.h"
|
||||
#include "TreasurePlacer.h"
|
||||
#include "../CRmgTemplate.h"
|
||||
#include "../CMapGenerator.h"
|
||||
#include "../Functions.h"
|
||||
#include "ObjectManager.h"
|
||||
@ -24,6 +25,7 @@
|
||||
#include "../../mapObjectConstructors/AObjectTypeHandler.h"
|
||||
#include "../../mapObjectConstructors/CObjectClassesHandler.h"
|
||||
#include "../../mapObjectConstructors/DwellingInstanceConstructor.h"
|
||||
#include "../../rewardable/Info.h"
|
||||
#include "../../mapObjects/CGHeroInstance.h"
|
||||
#include "../../mapObjects/CGPandoraBox.h"
|
||||
#include "../../mapObjects/CQuest.h"
|
||||
@ -37,15 +39,29 @@
|
||||
|
||||
VCMI_LIB_NAMESPACE_BEGIN
|
||||
|
||||
ObjectInfo::ObjectInfo():
|
||||
destroyObject([](CGObjectInstance * obj){})
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::process()
|
||||
{
|
||||
if (zone.getMaxTreasureValue() == 0)
|
||||
{
|
||||
//No treasures at all
|
||||
return;
|
||||
}
|
||||
|
||||
tierValues = generator.getConfig().pandoraCreatureValues;
|
||||
// Add all native creatures
|
||||
for(auto const & cre : VLC->creh->objects)
|
||||
{
|
||||
if(!cre->special && cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
creatures.push_back(cre.get());
|
||||
}
|
||||
}
|
||||
|
||||
// Get default objects
|
||||
addAllPossibleObjects();
|
||||
// Override with custom objects
|
||||
objects.patchWithZoneConfig(zone, this);
|
||||
|
||||
auto * m = zone.getModificator<ObjectManager>();
|
||||
if(m)
|
||||
createTreasures(*m);
|
||||
@ -62,14 +78,37 @@ void TreasurePlacer::init()
|
||||
|
||||
void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
|
||||
{
|
||||
if (oi.templates.empty())
|
||||
{
|
||||
logGlobal->error("Attempt to add ObjectInfo with no templates! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
if (!oi.generateObject)
|
||||
{
|
||||
logGlobal->error("Attempt to add ObjectInfo with no generateObject function! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
if (!oi.maxPerZone)
|
||||
{
|
||||
logGlobal->warn("Attempt to add ObjectInfo with 0 maxPerZone! Value: %d", oi.value);
|
||||
return;
|
||||
}
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
possibleObjects.push_back(oi);
|
||||
objects.addObject(oi);
|
||||
}
|
||||
|
||||
void TreasurePlacer::addAllPossibleObjects()
|
||||
{
|
||||
ObjectInfo oi;
|
||||
|
||||
addCommonObjects();
|
||||
addDwellings();
|
||||
addPandoraBoxes();
|
||||
addSeerHuts();
|
||||
addPrisons();
|
||||
addScrolls();
|
||||
}
|
||||
|
||||
void TreasurePlacer::addCommonObjects()
|
||||
{
|
||||
for(auto primaryID : VLC->objtypeh->knownObjects())
|
||||
{
|
||||
for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
|
||||
@ -83,21 +122,31 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
//Skip objects with per-map limit here
|
||||
continue;
|
||||
}
|
||||
ObjectInfo oi(primaryID, secondaryID);
|
||||
setBasicProperties(oi, CompoundMapObjectID(primaryID, secondaryID));
|
||||
|
||||
oi.generateObject = [this, primaryID, secondaryID]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create(map.mapInstance->cb, nullptr);
|
||||
};
|
||||
oi.value = rmgInfo.value;
|
||||
oi.probability = rmgInfo.rarity;
|
||||
oi.setTemplates(primaryID, secondaryID, zone.getTerrainType());
|
||||
oi.maxPerZone = rmgInfo.zoneLimit;
|
||||
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TreasurePlacer::setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const
|
||||
{
|
||||
oi.generateObject = [this, objid]() -> CGObjectInstance *
|
||||
{
|
||||
return VLC->objtypeh->getHandlerFor(objid)->create(map.mapInstance->cb, nullptr);
|
||||
};
|
||||
oi.setTemplates(objid.primaryID, objid.secondaryID, zone.getTerrainType());
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPrisons()
|
||||
{
|
||||
//Generate Prison on water only if it has a template
|
||||
auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType());
|
||||
if (!prisonTemplates.empty())
|
||||
@ -119,7 +168,7 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
size_t prisonsLeft = getMaxPrisons();
|
||||
for (int i = prisonsLevels - 1; i >= 0; i--)
|
||||
{
|
||||
ObjectInfo oi; // Create new instance which will hold destructor operation
|
||||
ObjectInfo oi(Obj::PRISON, 0); // Create new instance which will hold destructor operation
|
||||
|
||||
oi.value = generator.getConfig().prisonValues[i];
|
||||
if (oi.value > zone.getMaxTreasureValue())
|
||||
@ -157,22 +206,13 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TreasurePlacer::addDwellings()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
//all following objects are unlimited
|
||||
oi.maxPerZone = std::numeric_limits<ui32>::max();
|
||||
|
||||
std::vector<const CCreature *> creatures; //native creatures for this zone
|
||||
for(auto const & cre : VLC->creh->objects)
|
||||
{
|
||||
if(!cre->special && cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
creatures.push_back(cre.get());
|
||||
}
|
||||
}
|
||||
|
||||
//dwellings
|
||||
auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
|
||||
|
||||
@ -199,6 +239,9 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(cre->getFaction() == zone.getTownType())
|
||||
{
|
||||
auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction()));
|
||||
ObjectInfo oi(dwellingType, secondaryID);
|
||||
setBasicProperties(oi, CompoundMapObjectID(dwellingType, secondaryID));
|
||||
|
||||
oi.value = static_cast<ui32>(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
|
||||
oi.probability = 40;
|
||||
|
||||
@ -208,13 +251,20 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
obj->tempOwner = PlayerColor::NEUTRAL;
|
||||
return obj;
|
||||
};
|
||||
oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType());
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addScrolls()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
ObjectInfo oi(Obj::SPELL_SCROLL, 0);
|
||||
|
||||
for(int i = 0; i < generator.getConfig().scrollValues.size(); i++)
|
||||
{
|
||||
oi.generateObject = [i, this]() -> CGObjectInstance *
|
||||
@ -239,7 +289,22 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with gold
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxes()
|
||||
{
|
||||
if(zone.getType() == ETemplateZoneType::WATER)
|
||||
return;
|
||||
|
||||
addPandoraBoxesWithGold();
|
||||
addPandoraBoxesWithExperience();
|
||||
addPandoraBoxesWithCreatures();
|
||||
addPandoraBoxesWithSpells();
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithGold()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
for(int i = 1; i < 5; i++)
|
||||
{
|
||||
oi.generateObject = [this, i]() -> CGObjectInstance *
|
||||
@ -260,8 +325,11 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with experience
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithExperience()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
for(int i = 1; i < 5; i++)
|
||||
{
|
||||
oi.generateObject = [this, i]() -> CGObjectInstance *
|
||||
@ -282,49 +350,17 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
//pandora box with creatures
|
||||
const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
|
||||
|
||||
auto creatureToCount = [tierValues](const CCreature * creature) -> int
|
||||
{
|
||||
if(!creature->getAIValue() || tierValues.empty()) //bug #2681
|
||||
return 0; //this box won't be generated
|
||||
|
||||
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
|
||||
|
||||
int actualTier = creature->getLevel() > tierValues.size() ?
|
||||
tierValues.size() - 1 :
|
||||
creature->getLevel() - 1;
|
||||
float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
|
||||
if (creaturesAmount < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(creaturesAmount <= 5)
|
||||
{
|
||||
//No change
|
||||
}
|
||||
else if(creaturesAmount <= 12)
|
||||
{
|
||||
creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
|
||||
}
|
||||
else if(creaturesAmount <= 50)
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 5) * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 10) * 10;
|
||||
}
|
||||
return static_cast<int>(creaturesAmount);
|
||||
};
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithCreatures()
|
||||
{
|
||||
for(auto * creature : creatures)
|
||||
{
|
||||
int creaturesAmount = creatureToCount(creature);
|
||||
if(!creaturesAmount)
|
||||
continue;
|
||||
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
|
||||
oi.generateObject = [this, creature, creaturesAmount]() -> CGObjectInstance *
|
||||
{
|
||||
@ -339,12 +375,16 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
return obj;
|
||||
};
|
||||
oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
|
||||
oi.value = static_cast<ui32>((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3);
|
||||
oi.value = static_cast<ui32>(creature->getAIValue() * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount()));
|
||||
oi.probability = 3;
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addPandoraBoxesWithSpells()
|
||||
{
|
||||
ObjectInfo oi(Obj::PANDORAS_BOX, 0);
|
||||
//Pandora with 12 spells of certain level
|
||||
for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
|
||||
{
|
||||
@ -441,9 +481,14 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
oi.probability = 2;
|
||||
if(!oi.templates.empty())
|
||||
addObjectToRandomPool(oi);
|
||||
|
||||
}
|
||||
|
||||
void TreasurePlacer::addSeerHuts()
|
||||
{
|
||||
//Seer huts with creatures or generic rewards
|
||||
|
||||
ObjectInfo oi(Obj::SEER_HUT, 0);
|
||||
|
||||
if(zone.getConnectedZoneIds().size()) //Unlikely, but...
|
||||
{
|
||||
auto * qap = zone.getModificator<QuestArtifactPlacer>();
|
||||
@ -588,12 +633,6 @@ void TreasurePlacer::addAllPossibleObjects()
|
||||
}
|
||||
}
|
||||
|
||||
size_t TreasurePlacer::getPossibleObjectsSize() const
|
||||
{
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
return possibleObjects.size();
|
||||
}
|
||||
|
||||
void TreasurePlacer::setMaxPrisons(size_t count)
|
||||
{
|
||||
RecursiveLock lock(externalAccessMutex);
|
||||
@ -606,6 +645,40 @@ size_t TreasurePlacer::getMaxPrisons() const
|
||||
return maxPrisons;
|
||||
}
|
||||
|
||||
int TreasurePlacer::creatureToCount(const CCreature * creature) const
|
||||
{
|
||||
if(!creature->getAIValue() || tierValues.empty()) //bug #2681
|
||||
return 0; //this box won't be generated
|
||||
|
||||
//Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
|
||||
|
||||
int actualTier = creature->getLevel() > tierValues.size() ?
|
||||
tierValues.size() - 1 :
|
||||
creature->getLevel() - 1;
|
||||
float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
|
||||
if (creaturesAmount < 1)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if(creaturesAmount <= 5)
|
||||
{
|
||||
//No change
|
||||
}
|
||||
else if(creaturesAmount <= 12)
|
||||
{
|
||||
creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
|
||||
}
|
||||
else if(creaturesAmount <= 50)
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 5) * 5;
|
||||
}
|
||||
else
|
||||
{
|
||||
creaturesAmount = std::round(creaturesAmount / 10) * 10;
|
||||
}
|
||||
return static_cast<int>(creaturesAmount);
|
||||
};
|
||||
|
||||
bool TreasurePlacer::isGuardNeededForTreasure(int value)
|
||||
{// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed
|
||||
return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue;
|
||||
@ -623,6 +696,7 @@ std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo
|
||||
bool hasLargeObject = false;
|
||||
while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available
|
||||
{
|
||||
// FIXME: Pointer might be invalidated after this
|
||||
auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject);
|
||||
if(!oi) //fail
|
||||
break;
|
||||
@ -674,12 +748,21 @@ rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*>
|
||||
accessibleArea.add(int3());
|
||||
}
|
||||
|
||||
auto * object = oi->generateObject();
|
||||
if(oi->templates.empty())
|
||||
CGObjectInstance * object = nullptr;
|
||||
if (oi->generateObject)
|
||||
{
|
||||
logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
|
||||
oi->destroyObject(object);
|
||||
delete object;
|
||||
object = oi->generateObject();
|
||||
if(oi->templates.empty())
|
||||
{
|
||||
logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
|
||||
oi->destroyObject(object);
|
||||
delete object;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logGlobal->error("ObjectInfo has no generateObject function! Templates: %d", oi->templates.size());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -785,7 +868,7 @@ ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValu
|
||||
ui32 maxVal = desiredValue - currentValue;
|
||||
ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
|
||||
|
||||
for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
|
||||
for(ObjectInfo & oi : objects.getPossibleObjects()) //copy constructor turned out to be costly
|
||||
{
|
||||
if(oi.value > maxVal)
|
||||
break; //this assumes values are sorted in ascending order
|
||||
@ -859,24 +942,19 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
|
||||
boost::sort(treasureInfo, valueComparator);
|
||||
|
||||
//sort treasures by ascending value so we can stop checking treasures with too high value
|
||||
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
|
||||
{
|
||||
return oi1.value < oi2.value;
|
||||
});
|
||||
objects.sortPossibleObjects();
|
||||
|
||||
const size_t size = zone.area()->getTilesVector().size();
|
||||
|
||||
int totalDensity = 0;
|
||||
|
||||
// FIXME: No need to use iterator here
|
||||
for (auto t = treasureInfo.begin(); t != treasureInfo.end(); t++)
|
||||
{
|
||||
std::vector<rmg::Object> treasures;
|
||||
|
||||
//discard objects with too high value to be ever placed
|
||||
vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
|
||||
{
|
||||
return oi.value > t->max;
|
||||
});
|
||||
objects.discardObjectsAboveValue(t->max);
|
||||
|
||||
totalDensity += t->density;
|
||||
|
||||
@ -895,7 +973,11 @@ void TreasurePlacer::createTreasures(ObjectManager& manager)
|
||||
continue;
|
||||
}
|
||||
|
||||
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
|
||||
int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0,
|
||||
[](int v, const ObjectInfo* oi)
|
||||
{
|
||||
return v + oi->value;
|
||||
});
|
||||
|
||||
const ui32 maxPileGenerationAttempts = 2;
|
||||
for (ui32 attempt = 0; attempt < maxPileGenerationAttempts; attempt++)
|
||||
@ -1016,13 +1098,222 @@ char TreasurePlacer::dump(const int3 & t)
|
||||
return Modificator::dump(t);
|
||||
}
|
||||
|
||||
void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType)
|
||||
void TreasurePlacer::ObjectPool::addObject(const ObjectInfo & info)
|
||||
{
|
||||
auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
|
||||
if(!templHandler)
|
||||
return;
|
||||
|
||||
templates = templHandler->getTemplates(terrainType);
|
||||
possibleObjects.push_back(info);
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info)
|
||||
{
|
||||
/*
|
||||
Handle separately:
|
||||
- Dwellings
|
||||
- Prisons
|
||||
- Seer huts (quests)
|
||||
- Pandora Boxes
|
||||
*/
|
||||
// FIXME: This will drop all templates
|
||||
customObjects.insert(std::make_pair(CompoundMapObjectID(id, subid), info));
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp)
|
||||
{
|
||||
// FIXME: Wycina wszystkie obiekty poza pandorami i dwellami :?
|
||||
|
||||
// Copy standard objects if they are not already modified
|
||||
/*
|
||||
for (const auto & object : possibleObjects)
|
||||
{
|
||||
for (const auto & templ : object.templates)
|
||||
{
|
||||
// FIXME: Objects with same temmplates (Pandora boxes) are not added
|
||||
CompoundMapObjectID key(templ->id, templ->subid);
|
||||
if (!vstd::contains(customObjects, key))
|
||||
{
|
||||
customObjects[key] = object;
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
auto bannedObjectCategories = zone.getBannedObjectCategories();
|
||||
auto categoriesSet = std::unordered_set<ObjectConfig::EObjectCategory>(bannedObjectCategories.begin(), bannedObjectCategories.end());
|
||||
|
||||
if (categoriesSet.count(ObjectConfig::EObjectCategory::ALL))
|
||||
{
|
||||
possibleObjects.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
vstd::erase_if(possibleObjects, [this, &categoriesSet](const ObjectInfo & oi) -> bool
|
||||
|
||||
{
|
||||
auto category = getObjectCategory(oi.getCompoundID());
|
||||
if (categoriesSet.count(category))
|
||||
{
|
||||
logGlobal->info("Removing object %s from possible objects", oi.templates.front()->stringID);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
auto bannedObjects = zone.getBannedObjects();
|
||||
auto bannedObjectsSet = std::set<CompoundMapObjectID>(bannedObjects.begin(), bannedObjects.end());
|
||||
vstd::erase_if(possibleObjects, [&bannedObjectsSet](const ObjectInfo & object)
|
||||
{
|
||||
for (const auto & templ : object.templates)
|
||||
{
|
||||
CompoundMapObjectID key = object.getCompoundID();
|
||||
if (bannedObjectsSet.count(key))
|
||||
{
|
||||
// FIXME: Stopped working, nothing is banned
|
||||
logGlobal->info("Banning object %s from possible objects", templ->stringID);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
auto configuredObjects = zone.getConfiguredObjects();
|
||||
|
||||
// FIXME: Access TreasurePlacer from ObjectPool
|
||||
for (auto & object : configuredObjects)
|
||||
{
|
||||
tp->setBasicProperties(object, object.getCompoundID());
|
||||
addObject(object);
|
||||
logGlobal->info("Added custom object of type %d.%d", object.primaryID, object.secondaryID);
|
||||
}
|
||||
// TODO: Overwrite or add to possibleObjects
|
||||
|
||||
// FIXME: Protect with mutex as well?
|
||||
/*
|
||||
for (const auto & customObject : customObjects)
|
||||
{
|
||||
addObject(customObject.second);
|
||||
}
|
||||
*/
|
||||
// TODO: Consider adding custom Pandora boxes with arbitrary content
|
||||
}
|
||||
|
||||
std::vector<ObjectInfo> & TreasurePlacer::ObjectPool::getPossibleObjects()
|
||||
{
|
||||
return possibleObjects;
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::sortPossibleObjects()
|
||||
{
|
||||
boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
|
||||
{
|
||||
return oi1.value < oi2.value;
|
||||
});
|
||||
}
|
||||
|
||||
void TreasurePlacer::ObjectPool::discardObjectsAboveValue(ui32 value)
|
||||
{
|
||||
vstd::erase_if(possibleObjects, [value](const ObjectInfo& oi) -> bool
|
||||
{
|
||||
return oi.value > value;
|
||||
});
|
||||
}
|
||||
|
||||
ObjectConfig::EObjectCategory TreasurePlacer::ObjectPool::getObjectCategory(CompoundMapObjectID id)
|
||||
{
|
||||
auto name = VLC->objtypeh->getObjectHandlerName(id.primaryID);
|
||||
|
||||
if (name == "configurable")
|
||||
{
|
||||
auto handler = VLC->objtypeh->getHandlerFor(id.primaryID, id.secondaryID);
|
||||
if (!handler)
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::NONE;
|
||||
}
|
||||
|
||||
auto temp = handler->getTemplates().front();
|
||||
auto info = handler->getObjectInfo(temp);
|
||||
|
||||
if (info->hasGuards())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::CREATURE_BANK;
|
||||
}
|
||||
else if (info->givesResources())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::RESOURCE;
|
||||
}
|
||||
else if (info->givesArtifacts())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT;
|
||||
}
|
||||
else if (info->givesBonuses())
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
}
|
||||
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "dwelling" || name == "randomDwelling")
|
||||
{
|
||||
// TODO: Special handling for different tiers
|
||||
return ObjectConfig::EObjectCategory::DWELLING;
|
||||
}
|
||||
else if (name == "bank")
|
||||
return ObjectConfig::EObjectCategory::CREATURE_BANK;
|
||||
else if (name == "market")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "hillFort")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "resource" || name == "randomResource")
|
||||
return ObjectConfig::EObjectCategory::RESOURCE;
|
||||
else if (name == "randomArtifact") //"artifact"
|
||||
return ObjectConfig::EObjectCategory::RANDOM_ARTIFACT;
|
||||
else if (name == "artifact")
|
||||
{
|
||||
if (id.primaryID == Obj::SPELL_SCROLL ) // randomArtifactTreasure
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::SPELL_SCROLL;
|
||||
}
|
||||
else
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::QUEST_ARTIFACT;
|
||||
}
|
||||
}
|
||||
else if (name == "denOfThieves")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
else if (name == "lighthouse")
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
}
|
||||
else if (name == "magi")
|
||||
{
|
||||
// TODO: By default, both eye and hut are banned in every zone
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "mine")
|
||||
return ObjectConfig::EObjectCategory::RESOURCE_GENERATOR;
|
||||
else if (name == "pandora")
|
||||
return ObjectConfig::EObjectCategory::PANDORAS_BOX;
|
||||
else if (name == "prison")
|
||||
{
|
||||
// TODO: Prisons should be configurable
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
}
|
||||
else if (name == "questArtifact")
|
||||
{
|
||||
// TODO: There are no dedicated quest artifacts, needs extra logic
|
||||
return ObjectConfig::EObjectCategory::QUEST_ARTIFACT;
|
||||
}
|
||||
else if (name == "seerHut")
|
||||
{
|
||||
return ObjectConfig::EObjectCategory::SEER_HUT;
|
||||
}
|
||||
else if (name == "siren")
|
||||
return ObjectConfig::EObjectCategory::BONUS;
|
||||
else if (name == "obelisk")
|
||||
return ObjectConfig::EObjectCategory::OTHER;
|
||||
|
||||
// TODO: ObjectConfig::EObjectCategory::SPELL_SCROLL
|
||||
|
||||
// Not interesting for us
|
||||
return ObjectConfig::EObjectCategory::NONE;
|
||||
}
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -9,6 +9,8 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../ObjectInfo.h"
|
||||
#include "../Zone.h"
|
||||
#include "../../mapObjects/ObjectTemplate.h"
|
||||
|
||||
@ -18,21 +20,7 @@ class CGObjectInstance;
|
||||
class ObjectManager;
|
||||
class RmgMap;
|
||||
class CMapGenerator;
|
||||
|
||||
struct ObjectInfo
|
||||
{
|
||||
ObjectInfo();
|
||||
|
||||
std::vector<std::shared_ptr<const ObjectTemplate>> templates;
|
||||
ui32 value = 0;
|
||||
ui16 probability = 0;
|
||||
ui32 maxPerZone = 1;
|
||||
//ui32 maxPerMap; //unused
|
||||
std::function<CGObjectInstance *()> generateObject;
|
||||
std::function<void(CGObjectInstance *)> destroyObject;
|
||||
|
||||
void setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrain);
|
||||
};
|
||||
class ObjectConfig;
|
||||
|
||||
class TreasurePlacer: public Modificator
|
||||
{
|
||||
@ -45,11 +33,27 @@ public:
|
||||
|
||||
void createTreasures(ObjectManager & manager);
|
||||
void addObjectToRandomPool(const ObjectInfo& oi);
|
||||
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
|
||||
void setBasicProperties(ObjectInfo & oi, CompoundMapObjectID objid) const;
|
||||
|
||||
// TODO: Can be defaulted to addAllPossibleObjects, but then each object will need to be configured
|
||||
void addCommonObjects();
|
||||
void addDwellings();
|
||||
void addPandoraBoxes();
|
||||
void addPandoraBoxesWithGold();
|
||||
void addPandoraBoxesWithExperience();
|
||||
void addPandoraBoxesWithCreatures();
|
||||
void addPandoraBoxesWithSpells();
|
||||
void addSeerHuts();
|
||||
void addPrisons();
|
||||
void addScrolls();
|
||||
void addAllPossibleObjects(); //add objects, including zone-specific, to possibleObjects
|
||||
// TODO: Read custom object config from zone file
|
||||
|
||||
/// Get all objects for this terrain
|
||||
|
||||
size_t getPossibleObjectsSize() const;
|
||||
void setMaxPrisons(size_t count);
|
||||
size_t getMaxPrisons() const;
|
||||
int creatureToCount(const CCreature * creature) const;
|
||||
|
||||
protected:
|
||||
bool isGuardNeededForTreasure(int value);
|
||||
@ -59,7 +63,26 @@ protected:
|
||||
rmg::Object constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement = false);
|
||||
|
||||
protected:
|
||||
std::vector<ObjectInfo> possibleObjects;
|
||||
class ObjectPool
|
||||
{
|
||||
public:
|
||||
void addObject(const ObjectInfo & info);
|
||||
void updateObject(MapObjectID id, MapObjectSubID subid, ObjectInfo info);
|
||||
std::vector<ObjectInfo> & getPossibleObjects();
|
||||
void patchWithZoneConfig(const Zone & zone, TreasurePlacer * tp);
|
||||
void sortPossibleObjects();
|
||||
void discardObjectsAboveValue(ui32 value);
|
||||
|
||||
ObjectConfig::EObjectCategory getObjectCategory(CompoundMapObjectID id);
|
||||
|
||||
private:
|
||||
|
||||
std::vector<ObjectInfo> possibleObjects;
|
||||
std::map<CompoundMapObjectID, ObjectInfo> customObjects;
|
||||
|
||||
} objects;
|
||||
// TODO: Need to nagivate and update these
|
||||
|
||||
int minGuardedValue = 0;
|
||||
|
||||
rmg::Area treasureArea;
|
||||
@ -67,6 +90,9 @@ protected:
|
||||
rmg::Area guards;
|
||||
|
||||
size_t maxPrisons;
|
||||
|
||||
std::vector<const CCreature *> creatures; //native creatures for this zone
|
||||
std::vector<int> tierValues;
|
||||
};
|
||||
|
||||
VCMI_LIB_NAMESPACE_END
|
||||
|
@ -197,7 +197,7 @@ EffectTarget UnitEffect::transformTargetByChain(const Mechanics * m, const Targe
|
||||
|
||||
auto possibleTargets = m->battle()->battleGetUnitsIf([&](const battle::Unit * unit) -> bool
|
||||
{
|
||||
return isValidTarget(m, unit);
|
||||
return isReceptive(m, unit) && isValidTarget(m, unit);
|
||||
});
|
||||
|
||||
for(const auto *unit : possibleTargets)
|
||||
|
@ -2052,22 +2052,22 @@
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="713"/>
|
||||
<source>Roads</source>
|
||||
<translation type="unfinished">Estradas</translation>
|
||||
<translation>Estradas</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="728"/>
|
||||
<source>Dirt</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Terra</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="748"/>
|
||||
<source>Gravel</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Cascalho</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="768"/>
|
||||
<source>Cobblestone</source>
|
||||
<translation type="unfinished"></translation>
|
||||
<translation>Paralelepípedo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../windownewmap.ui" line="813"/>
|
||||
|
@ -1194,7 +1194,7 @@ void CGameHandler::visitCastleObjects(const CGTownInstance * t, std::vector<cons
|
||||
|
||||
for (auto & building : t->rewardableBuildings)
|
||||
{
|
||||
if (!t->town->buildings.at(building.first)->manualHeroVisit)
|
||||
if (!t->town->buildings.at(building.first)->manualHeroVisit && t->hasBuilt(building.first))
|
||||
buildingsToVisit.push_back(building.first);
|
||||
}
|
||||
|
||||
@ -2718,15 +2718,15 @@ bool CGameHandler::switchArtifactsCostume(const PlayerColor & player, const Obje
|
||||
// Second, find the necessary artifacts for the costume
|
||||
for(const auto & artPos : costumeArtMap)
|
||||
{
|
||||
if(const auto availableArts = artFittingSet.getAllArtPositions(artPos.second, false, false, false); !availableArts.empty())
|
||||
if(const auto slot = artFittingSet.getArtPos(artPos.second, false, false); slot != ArtifactPosition::PRE_FIRST)
|
||||
{
|
||||
bma.artsPack0.emplace_back(BulkMoveArtifacts::LinkedSlots
|
||||
{
|
||||
artSet->getArtPos(artFittingSet.getArt(availableArts.front())),
|
||||
artSet->getArtPos(artFittingSet.getArt(slot)),
|
||||
artPos.first
|
||||
});
|
||||
artFittingSet.removeArtifact(availableArts.front());
|
||||
if(ArtifactUtils::isSlotBackpack(availableArts.front()))
|
||||
artFittingSet.removeArtifact(slot);
|
||||
if(ArtifactUtils::isSlotBackpack(slot))
|
||||
estimateBackpackSize--;
|
||||
}
|
||||
}
|
||||
@ -3757,9 +3757,10 @@ bool CGameHandler::swapStacks(const StackLocation & sl1, const StackLocation & s
|
||||
}
|
||||
}
|
||||
|
||||
bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble)
|
||||
bool CGameHandler::putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional<bool> askAssemble)
|
||||
{
|
||||
assert(art && art->artType);
|
||||
const auto artInst = getArtInstance(id);
|
||||
assert(artInst && artInst->artType);
|
||||
ArtifactLocation dst(al.artHolder, ArtifactPosition::PRE_FIRST);
|
||||
dst.creature = al.creature;
|
||||
auto putTo = getArtSet(al);
|
||||
@ -3767,11 +3768,11 @@ bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInsta
|
||||
|
||||
if(al.slot == ArtifactPosition::FIRST_AVAILABLE)
|
||||
{
|
||||
dst.slot = ArtifactUtils::getArtAnyPosition(putTo, art->getTypeId());
|
||||
dst.slot = ArtifactUtils::getArtAnyPosition(putTo, artInst->getTypeId());
|
||||
}
|
||||
else if(ArtifactUtils::isSlotBackpack(al.slot) && !al.creature.has_value())
|
||||
{
|
||||
dst.slot = ArtifactUtils::getArtBackpackPosition(putTo, art->getTypeId());
|
||||
dst.slot = ArtifactUtils::getArtBackpackPosition(putTo, artInst->getTypeId());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3786,10 +3787,9 @@ bool CGameHandler::putArtifact(const ArtifactLocation & al, const CArtifactInsta
|
||||
askAssemble = false;
|
||||
}
|
||||
|
||||
if(art->canBePutAt(putTo, dst.slot))
|
||||
if(artInst->canBePutAt(putTo, dst.slot))
|
||||
{
|
||||
PutArtifact pa(dst, askAssemble.value());
|
||||
pa.art = art;
|
||||
PutArtifact pa(id, dst, askAssemble.value());
|
||||
sendAndApply(&pa);
|
||||
return true;
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ public:
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * artType, const SpellID & spellId, const ArtifactPosition & pos);
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override;
|
||||
bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override;
|
||||
bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble) override;
|
||||
bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional<bool> askAssemble) override;
|
||||
void removeArtifact(const ArtifactLocation &al) override;
|
||||
void removeArtifact(const ObjectInstanceID & srcId, const std::vector<ArtifactPosition> & slotsPack);
|
||||
bool moveArtifact(const PlayerColor & player, const ArtifactLocation & src, const ArtifactLocation & dst) override;
|
||||
|
@ -72,7 +72,7 @@ public:
|
||||
|
||||
bool giveHeroNewArtifact(const CGHeroInstance * h, const ArtifactID & artId, const ArtifactPosition & pos) override {return false;}
|
||||
bool giveHeroNewScroll(const CGHeroInstance * h, const SpellID & spellId, const ArtifactPosition & pos) override {return false;}
|
||||
bool putArtifact(const ArtifactLocation & al, const CArtifactInstance * art, std::optional<bool> askAssemble) override {return false;}
|
||||
bool putArtifact(const ArtifactLocation & al, const ArtifactInstanceID & id, std::optional<bool> askAssemble) override {return false;}
|
||||
void removeArtifact(const ArtifactLocation &al) override {}
|
||||
bool moveArtifact(const PlayerColor & player, const ArtifactLocation & al1, const ArtifactLocation & al2) override {return false;}
|
||||
|
||||
|
Reference in New Issue
Block a user