-- Gallery #7 (Total-conversion team): the smallest thing that is -- recognizably a different game. Its own boot config, its own title -- screen, its own three-species dex, its own single badge, and one map. -- -- LEGAL POSTURE (constraint 1): the Red import still runs and supplies the -- fallback infrastructure this conversion sits on -- the OVERWORLD tileset, -- the font, the move table. The conversion overrides on top. Every pixel -- under assets/ is original work generated by tools/make_assets.py; nothing -- here transforms extracted Red art into "new" content. local MAP = "SABLE_COVE" local BADGE = "SABLE_TIDE_BADGE" -- three original species; ids are namespaced so a mixed load cannot -- collide with Red's local DEX = { { id = "SABLE_EMBERKIT", name = "EMBERKIT", dex = 1, art = "emberkit", types = { "FIRE" }, stats = { hp = 45, attack = 60, defense = 40, speed = 65, special = 50 } }, { id = "SABLE_TIDEPUP", name = "TIDEPUP", dex = 2, art = "tidepup", types = { "WATER" }, stats = { hp = 50, attack = 48, defense = 60, speed = 45, special = 55 } }, { id = "SABLE_MOSSLING", name = "MOSSLING", dex = 3, art = "mossling", types = { "GRASS", "POISON" }, stats = { hp = 55, attack = 50, defense = 55, speed = 40, special = 60 } }, } return function(mod) local ChipAsm = require("src.audio.ChipAsm") -- ------- species, cries and icons for _, entry in ipairs(DEX) do mod.content.pokemon:register(entry.id, { id = entry.id, name = entry.name, dex = entry.dex, types = entry.types, baseStats = entry.stats, catchRate = 190, baseExp = 64, growthRate = "MEDIUM_FAST", level1Moves = { "TACKLE" }, learnset = { { level = 7, move = "GROWL" }, { level = 13, move = "QUICK_ATTACK" }, }, evolutions = {}, spriteFront = mod.path .. "/assets/" .. entry.art .. "_front.png", spriteBack = mod.path .. "/assets/" .. entry.art .. "_back.png", frontSize = 5, cry = entry.id, icon = { image = mod.path .. "/assets/" .. entry.art .. "_icon.png", frames = 2 }, }) -- one authored chip effect per species, keyed by species id exactly -- like the vanilla cry table mod.content.cries:register(entry.id, { chip = ChipAsm.sfx{ channels = { { hw = 1, program = { { pitchSweep = { pace = 2 + entry.dex, subtract = entry.dex == 2, shift = 3 } }, { squareNote = { len = 5, volume = 13, fade = 2, frequency = 0x480 + entry.dex * 0x60 } }, } } }, }.chip, pitch = 128, length = 128, }) mod.content.icons:register(entry.id, { image = mod.path .. "/assets/" .. entry.art .. "_icon.png", frames = 2, }) end -- ------- the badge, which is an item like every vanilla badge mod.content.items:register(BADGE, { id = BADGE, name = "TIDEBADGE", price = 0, keyItem = true, tossable = false, }) -- ------- the rules the engine used to hard-code -- deep registry: register and patch are the same verb, and only the keys -- named here move. Everything else keeps its imported value. mod.content.constants:patch("dexSize", #DEX) mod.content.constants:patch("dexDigits", 1) mod.content.constants:patch("levelCap", 50) -- override, not patch: under deep semantics a list APPENDS, so patching -- here would leave Kanto's eight badges in front of this one. override -- is the verb that drops a list, which is exactly what a conversion wants mod.content.constants:override("badges", { { id = BADGE, name = "TIDE" } }) mod.content.constants:override("hmMoves", { "CUT", "SURF" }) -- ------- the one map local blocks = {} for i = 1, 10 * 9 do blocks[i] = 1 end mod.content.maps:register(MAP, { id = MAP, label = "SableCove", index = 1000, tileset = "OVERWORLD", width = 10, height = 9, blocks = blocks, borderBlock = 11, warps = {}, objects = {}, signs = {}, }) mod.content.encounters:register(MAP, { grass = { rate = 25, slots = { { level = 3, species = "SABLE_EMBERKIT" }, { level = 3, species = "SABLE_TIDEPUP" }, { level = 3, species = "SABLE_MOSSLING" }, { level = 4, species = "SABLE_TIDEPUP" }, { level = 4, species = "SABLE_MOSSLING" }, { level = 5, species = "SABLE_EMBERKIT" }, { level = 5, species = "SABLE_TIDEPUP" }, { level = 5, species = "SABLE_MOSSLING" }, { level = 6, species = "SABLE_EMBERKIT" }, { level = 6, species = "SABLE_MOSSLING" }, } }, }) -- ------- the new game itself mod.content.field:patch("boot", { startMap = MAP, startX = 5, startY = 4, startFacing = "down", playerName = "SABLE", rivalName = "CORAL", startMoney = 1500, lastHeal = { map = MAP, x = 5, y = 4 }, namePresets = { player = { "SABLE", "WREN", "PIKE" }, rival = { "CORAL", "REEF", "SHOAL" } }, -- the conversion owns the boot flow; splash and newGame keep the -- engine screens, which is the point of naming them individually screens = { title = "SableTitle" }, }) -- ------- the title screen mod.content.screens:register("SableTitle", { new = function(game, opts) opts = opts or {} local Font = mod.ui.Font local state = { game = game, isOpaque = true, blink = 0 } function state:update() self.blink = (self.blink + 1) % 60 local input = game.input if not input then return end if input:wasPressed("a") or input:wasPressed("start") then if opts.onNewGame then opts.onNewGame() end end end function state:draw() love.graphics.setColor(1, 1, 1, 1) love.graphics.rectangle("fill", 0, 0, 160, 144) love.graphics.setColor(0, 0, 0, 1) Font.draw("SABLE COVE", 40, 40) Font.draw("A MINI CONVERSION", 12, 56) if self.blink < 40 then Font.draw("PRESS A", 52, 104) end end return state end, }) mod.events:on("game.ready", function(ev) -- the payload carries the live Game; a conversion uses it to check -- that its own boot config actually took local boot = ev.game and ev.game.data and ev.game.data.field and ev.game.data.field.boot if not (boot and boot.startMap == MAP) then mod.log:warn("another mod owns field.boot; raise this mod's priority " .. "above %s to win the merge", tostring(boot and boot.startMap)) end end) end