Jump to content
Double Fine Action Forums
Sign in to follow this  
Nikkelitous

My wishlist

Recommended Posts

These are my wishlist items for this game (even the prototype)

1: Have the savestate be more complete (include global variables)

2: Have the savestate saved to disk (maybe with a particular tool you pick up)

3: Have a console accessable in game (again, maybe with a particular tool)

4: Have an in-game "file editor" letting us change lua files.

5: World tree as a point for more quests.

These things would enable a much more robust game and hacking environment.

These requests do not require the attention of the original devs. Actually anyone familiar with Lua could do any of these (except for MAYBE the save state, that might be held in the C code, though maybe a whole rebuild of the save state system could be done in Lua)

Share this post


Link to post
Share on other sites

Unfortunately SaveLoad.lua crashes both lua decompilers I've tried so hard to look into what's in there.

Looks like adding new items is pretty easy, but to get it to properly accept typed input a bit more work, but probably doable. Confirmed the interpreter has a working loadstring function (lua's eval) so executing a command would be fine.

Building a full file editor in the game that's actually usable would be a bunch more work though. As would building out new areas.

Share this post


Link to post
Share on other sites

It turns out that building new areas is easy with the tiled editor. Accepting input may be hard. Building a new save state engine seems easier and easier. You just trying to track down all variables used...

Share this post


Link to post
Share on other sites

Actually there's some good examples of accepting input given the PortHack and Dialog classes. I've now got a partially working version. Problem is that when you're in console at the the game is still processing other inputs. Hard to work out how to turn that off without more knowledge of MOAI.

Share this post


Link to post
Share on other sites

Actually that is the exact problem I was worried about. I'm thinking maybe surrounding the character with collision walls might be the best option.

Share this post


Link to post
Share on other sites

Not sure that'd be good enough in my current version, the problem isn't that the character is moving while the console is up, its that its half processing some of the input and getting stuck in a keydown state on movement keys. Might just be a bug in my code or something.

Share this post


Link to post
Share on other sites

Yeah I'd definately tried that... or at least thought I had. It turns out that the key state that was often getting stuck on was Shift-S/etc rather than lower s. This is an existing bug in the game that acts a bit like an autorun. Can clear it by repeating the shifted state most of the time. Now have a working Console it seems :)

Share this post


Link to post
Share on other sites

Yeay for console! Also wondering how they did the controls during the cable. That might tell you how to grab controls exclusively.

Share this post


Link to post
Share on other sites

I think there may be a special case for Q and E, but it doesn't apply to WSAD, or at least not the shifted versions. Although that may just be that different systems are in place to block those events. I based my code on some of the code used for cable+dialogs that put the game into a partly paused state. When I take that out the Q key becomes active during the console and the cable shoots out, so clearly there's some sort of filtering going on there.

Share this post


Link to post
Share on other sites

It may be that the cable script is using the same method to grab the Q key. That could lead to double buttons. Maybe "disengage" the cable while the console is running?

Share this post


Link to post
Share on other sites

Ok here goes..hope posting this here is ok...

Add Data\Scripts\InputBox.lua

local Font = require("Font")

local Menu = require("Menu")

local Music = require("Music")

local Graphics = require("DFCommon.Graphics")

local GameState = require("GameState")

local InputBox = require("Class").create(Menu)

InputBox.x0, InputBox.y0, InputBox.x1, InputBox.y1 = 150, 150, 874, 450

function InputBox:init(rWorld, bPlayCue)

 Menu.init(self, rWorld.rUiViewport)

 self._rWorld = rWorld

 table.insert(self._rWorld.rRoom.tLayers, self.rLayer)

 if bPlayCue then

   self:playCue("HackandSlash/UI/NPC_Interact")

 end

end

function InputBox:destroy()

 table.remove(self._rWorld.rRoom.tLayers, #self._rWorld.rRoom.tLayers)

end

function InputBox:editText(sText, bEdit)

 local rTextStyle = MOAITextStyle.new()

 local rFont

 if GameState.tValues.bDecodeSymbolic then

   rFont = Font.rDecodedFont

 else

   rFont = Font.rSymbolicFont

 end

 rTextStyle:setFont(rFont)

 rTextStyle:setSize(16)

 self:addBackground(self.x0, self.y0, self.x1, self.y1)

 local rTextBox = self:addTextBox(self.x0, self.y0, self.x1, self.y1, rTextStyle, sText)

 rTextBox:setPriority(EDIT_DATA_PRIO)

 local rKeyboard = MOAIInputMgr.device.keyboard

 local bExit = false

 local sInput = ""

 local sKeys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 =.)[]'\"/\\+-*~,"

 while not bExit do

   coroutine.yield()

   for i = 1, #sKeys do

     local c = sKeys:sub(i, i)

     if rKeyboard:keyDown(c) then

       sInput = sInput .. c

   rTextBox:setString(sInput)

     end

   end

   if rKeyboard:keyDown("\b") and #sInput > 0 then

     sInput = sInput:sub(0, #sInput - 1)

     rTextBox:setString(sInput)

   end

   if rKeyboard:keyDown("\r") then

     bExit = true

   end

 end

 rKeyboard:setCallback(nil)

 self.rLayer:clear()

 return sInput

end

return InputBox

Add Data\Scripts\Items\Console.lua

local Item = require("Item")

local Dialog = require("Dialog")

local GameState = require("GameState")

local Font = require("Font")

local Console = require("Class").create(Item)

Console.init = function(l_1_0, l_1_1)

 Item.init(l_1_0, l_1_1, "Console")

end



Console.use = function(self)

  self.rInventory.rWorld:runInputBox(function(box)

    local sInput = box:editText("> ", false)



    print("Console running: " .. sInput)

    local func = loadstring(sInput)

    local result

    if func ~= nil then

      local context = { rWorld = self.rInventory.rWorld }

      setmetatable(context, { __index = _G })

      setfenv(func, context)

      result = func()

      if result ~= nil then

        result = tostring(result)

      end

    else

      result = "Error"

    end



    if result ~= nil and result ~= "nil" then

      print("Result: " .. result)

      box:editText(result, false)

    end



  end)

end



return Console

Replace Data\Scripts\Rooms\Cave\Main.lua with this (just a few added lines)

local GameState = require("GameState")

local Room = require("Room")

local Main = require("Class").create(Room)

Main.init = function(self, l_1_1, l_1_2, l_1_3)

 Room.init(self, l_1_1, l_1_2, l_1_3)

 local rWizardInteract = self.tEntitiesByName["Wizard Interact"]

 rWizardInteract.dInteract:register(self._onWizardInteract, self)

end



Main._onWizardInteract = function(self)

 if not GameState.tValues.bMetWizard then

   GameState.tValues.bMetWizard = true

   self.rWorld:runDialog(function(dialog)

     dialog:sayLine("Wizard", "Hey kid! It's nice of you to visit.")

     dialog:sayLine("Wizard", "Do you remember Jinn? The king finally caught up with him.")

     dialog:sayLine("Wizard", "They're holding him in the castle prison.")

     dialog:sayLine("Wizard", "Do you think you could pay him a visit?")

     dialog:sayLine("Wizard", "I can't go myself, or they'll throw me right in prison with him.")

     dialog:sayLine("Wizard", "It's dangerous to go alone, though. The forest is full of monsters these days.")

     dialog:sayLine("Wizard", "Download this! I coded up something pretty awesome.")

     self.rWorld.rInventory:addItem("LaptopRecord")

     dialog:displayItem("LaptopRecord", "You got recording software! This will snapshot the entire world on your laptop. Press 1 to use it.")

     self.rWorld.rInventory:addItem("LaptopRestore")

     dialog:displayItem("LaptopRestore", "You got playback software! This will reload snapshots you've captured of the world state. Press 2 to use it.")

     dialog:sayLine("Wizard", "Oh, and could you give him this note?")

     self.rWorld.rInventory:addItem("WizardNote")

     dialog:displayItem("Note", "You got a wizard note! That's worth 1 wizard note!")

     self.rWorld.rInventory:addItem("Console")

     dialog:displayItem("Console", "You got a debug console!")

     GameState.tValues.bDecodeSymbolic = true

     end)

 else

   self.rWorld:runDialog(function(self)

   self:sayLine("Wizard", "Have you visited Jinn yet?")

  end)

 end

end



return Main

Edits: updated Console.lua so rWorld instance is passed into scope

Share this post


Link to post
Share on other sites

Replace Data\Scripts\World.lua with this (one new function):

local Graphics = require("DFCommon.Graphics")

local File = require("DFCommon.File")

local Room = require("Room")

local Dialog = require("Dialog")

local InputBox = require("InputBox")

local PortHack = require("PortHack")

local Inventory = require("Inventory")

local World = require("Class").create()

function World:init(rViewport, rUiViewport, bAllItems)

 self.rViewport = rViewport

 self.rUiViewport = rUiViewport

 self.rUiLayer = MOAILayer.new()

 self.rUiLayer:setViewport(self.rUiViewport)

 local rWatermarkProp = MOAIProp.new()

 local rDeck = Graphics.loadSpriteSheet("AF/AF")

 rWatermarkProp:setLoc(896, 128)

 rWatermarkProp:setDeck(rDeck)

 rWatermarkProp:setIndex(rDeck.names.watermark)

 rWatermarkProp:setScl(1, -1)

 self.rUiLayer:insertProp(rWatermarkProp)

 self._bPaused = false

 self.rInventory = Inventory.new(self)

 if bAllItems then

   self.rInventory:addItem("LaptopRecord")

   self.rInventory:addItem("LaptopRestore")

   self.rInventory:addItem("TimeSlow")

   self.rInventory:addEquipment("Cable")

 end

 self.rRoom = nil

 self.sPrevRoomPath = nil

end

function World:setRoom(sRoomPath)

 local tRoomData = dofile(File.getAssetPath("Rooms/" .. sRoomPath .. ".lua"))

 local rRoomClass = Room

 local sClassName = "Rooms." .. string.gsub(sRoomPath, "/", ".")

 local bLoaded, rClass = pcall(require, sClassName)

 if bLoaded then

   rRoomClass = rClass

 end

 local rRoom = rRoomClass.new(self, sRoomPath, tRoomData)

 self:runRoom(rRoom)

end

function World:runRoom(rRoom)

 if self.rRoom then

   self.rRoom:stop()

   self.sPrevRoomPath = self.rRoom.sPath

 end

 self.rRoom = rRoom

 self.rRoom:start(self.rViewport, self.rUiLayer)

end

function World:setRoomInstance(rRoom)

 if self.rRoom then

   MOAIRenderMgr.setRenderTable({})

   self.rRoom.rPhysicsWorld:stop()

 end

 self.rRoom = rRoom

 if rRoom then

   assert(rRoom.rPhysicsWorld:isActive())

   MOAIRenderMgr.setRenderTable(rRoom.tLayers)

 end

end

function World:runDialog(fnDialogHandler, bPlayCue)

 if bPlayCue == nil then

   bPlayCue = true

 end

 self:pause(true)

 local rDialogThread = MOAICoroutine.new()

 rDialogThread:run(function()

   local rDialog = Dialog.new(self, bPlayCue)

   fnDialogHandler(rDialog)

   rDialog:destroy()

   self:pause(false)

 end)

end

function World:runInputBox(fnInputBoxHandler, bPlayCue)

 if bPlayCue == nil then

   bPlayCue = true

 end

 self:pause(true)

 local rInputBoxThread = MOAICoroutine.new()

 rInputBoxThread:run(function()

   local rInputBox = InputBox.new(self, bPlayCue)

   fnInputBoxHandler(rInputBox)

   rInputBox:destroy()

   self:pause(false)

 end)

end

function World:runPortHack(rObject, rBaseClass)

 self:pause(true)

 local rPortHackThread = MOAICoroutine.new()

 rPortHackThread:run(function()

   local rPortHack = PortHack.new(self)

   rPortHack:run(rObject, rBaseClass)

   rPortHack:destroy()

   self:pause(false)

 end)

end

function World:pause(bPause)

 self._bPaused = bPause

 self.rRoom.rPhysicsWorld:pause(bPause)

end

function World:tick()

 self.rInventory:tick(self._bPaused)

 if not self._bPaused and self.rRoom then

   self.rRoom:tick()

 end

end

return World

Share this post


Link to post
Share on other sites

Some sample cases, unfortunately in the current version the eval scope doesn't have any packages so you need to require("...") as needed.

require("Character").fSpriteScale=5

require("GameState").tValues.bDecodeSymbolic = false

require("Characters.HeroCharacter").iTilesPerSecond = 30

for k,v in pairs(require("GameState").tValues) do print(k,v) end

Share this post


Link to post
Share on other sites

This is a seriously great thread. Stuff like earning access to an in-game console would be totally great for a fuller version of the game. It blows me away that you're already working on stuffing that into the prototype.

If you're curious about what hit the cutting room floor for the prototype:

World Tree

The World Tree is meant to be a procedural room like the library. The top-level room was meant to correspond to g_rWorld, and each interior branch was supposed to recursively descend to each field stored on the world object. When you descended to a non-table field, there was going to be a pedestal blocking that root that allowed you to manipulate the value.

It didn't tie into any puzzles, but it would've been another cool physical manifestation of the game's simulation state, and you would've been able to make some really neat, more persistent hacks than you could with the data cable.

Right now, it's just an empty shell. I've got half of the implementation sitting in a shelved changelist at work. I'd love to get it in for a patch if we wind up having time to make one.

Data Library

There was meant to be a separate library for the data files, where the compiled lua bytecode is stored, with a tougher entrance puzzle in front of it. This would let you access books corresponding to each of the actual code files. I cut this library because we didn't have time to implement the New Game+ treasures.

New Game+ treasures

When you beat the game the first time, I wanted to spawn you back at the beginning with a couple of more sophisticated hacking tools. The first was a magic pen, which would turn the book reading interface into a book writing interface.

The second was a decoder ring that turned the byte code of any lua data file you were carrying around into a human-readable assembly listing. I wanted to do something goofy like write the assembler comments in fantasy prose.


getglobal 0 0 ; And lo, the variable hailed globally as 'g_rWorld' was placed onto the Stack.

A bugfixing pass :P

The game has some unfortunate game state bugs that we didn't have enough time to flush out thoroughly. These are particularly bad in a game like Hack n' Slash because it can be hard to tell whether they're intentional or not, making them extra confusing. Other than the glitches to the left of the castle, they're not intentional.

Share this post


Link to post
Share on other sites
The second was a decoder ring that turned the byte code of any lua data file you were carrying around into a human-readable assembly listing. I wanted to do something goofy like write the assembler comments in fantasy prose.


getglobal 0 0 ; And lo, the variable hailed globally as 'g_rWorld' was placed onto the Stack.

I love it!

Share this post


Link to post
Share on other sites

This is pretty awesome, I don't have any background in Lua, but it looks like I might add it to my repertoire just to mess around in this game. @Nerago, are there issues with adding items like this? I'm concerned about what happens when they don't have an appropriate icon. @Brandon, just to check I'm assuming we are all ok to mess around with the prototype and add stuff here right? And was the World tree supposed to be implemented in Lua?

Share this post


Link to post
Share on other sites

All maps are done using tiled and a Lua script. In fact, the map themselves are saved as Lua files.

Also, since this game was about hacking, i'm pretty sure they whole crew is going to support anything we do to it (at least until they decide to turn it into a full game)

Share this post


Link to post
Share on other sites
@Nerago, are there issues with adding items like this? I'm concerned about what happens when they don't have an appropriate icon.
I didn't really dig into that part of the code but the Console item I added got a book icon automatically, possibly that's the default case when there's nothing specific defined. Or else the game randomly ended up with that by accident and was lucky not to crash. I'm sure there's some way to override the icon, but the most obvious place under Win\Munged\Items\ seems like old code with some leftover programmer art, rather than live assets.

Share this post


Link to post
Share on other sites

@Nerago, Data\Scripts\Rooms\Cave\Main.lua and Data\Scripts\World.lua are both compiled, are there any issues with it interpreting your changes or do I need to recompile it?

Massive lack of Lua experience :(... I'll need to sit down with it over Christmas =D...

Share this post


Link to post
Share on other sites

Nah sorry I should have written better instructions. Replacing the compiled with with a non-compiled version works fine. I gave you complete copies of the decompiled files rather than just the changes, since you'd have had to decompile to add the changes, but instead you should just be able to replace the file. I haven't tried with Notepad but you might be able to just open the file, clear the contents and paste my version, otherwise try deleting and recreating.

You might want to move on to decompiling some of the other files to get a better idea of what the various objects and fields to manipulate are.

Share this post


Link to post
Share on other sites

Sweet!, It would be good to get an idea of what stuff is hardcoded in actually... Assuming that DF has no issues in us playing around with everything, I'm really curious what can be done with what we have =D...

I'm also going to see how hardened this is to editing, I'll try and change it while it's running ;)...

Share this post


Link to post
Share on other sites
Sign in to follow this  

×
×
  • Create New...