DFHack / dfhack

Memory hacking library for Dwarf Fortress and a set of tools that use it
Other
1.86k stars 468 forks source link

widget for world map view #908

Open warmist opened 8 years ago

warmist commented 8 years ago

Create new widget for displaying world just like in embark screen. It shouldn't be that hard (famous last words)

PatrikLundell commented 7 years ago

I just happen to have made a Grid widget that allows you to address individual coordinates to write characters with and without pen info. As far as I can see it works, although I haven't actually tested the viewport functionality. I've used it in my regionmanipulator.lua script (https://www.dropbox.com/s/l0jkgn27zrf2xh4/regionmanipulator.lua?dl=0) where a 16*16 embark region grid is represented.

lethosor commented 7 years ago

I took a look at that. There's a native dfhack.penarray class that you could probably use, which would be faster and supports clipping (but currently not resizing). You should also be using error() for errors, or dfhack.printerr() if that isn't appropriate (which will at least avoid having to call dfhack.color() twice).

PatrikLundell commented 7 years ago

Excellent advice as usual, lethosor! I reworked it and it seems to be working as it intended (the widget added at the end of the post).

You probably can't replicate the world map exactly because DF seems to use multiple tiles for the same biome to create variation. However, using Ragundo's code for biome determination and a map of biome to character you can recreate the biomes (I've done it, using my own character map, to do this in my showbiomes and tweakmap tools, using Ragundo's logic for biome determination). You'd then have to add volcanoes, settlements, peaks, caves, and rivers (and possibly something on top of that).

Reworked Grid widget (viewport still completely untested):


--================================================================
--  The Grid widget defines an pen supporting X/Y character display grid supporting display of
--  a grid larger than the frame allows through a panning viewport. The init function requires
--  the specification of the width and height attributes that defines the grid dimensions.
--  The grid coordinates are 0 based.
--
Grid = defclass (Grid, widgets.Widget)
Grid.ATTRS = 
  {width = DEFAULT_NIL,
   height = DEFAULT_NIL}

--================================================================

function Grid:init ()
  if type (self.width) ~= 'number' or
     type (self.height) ~= 'number' or
     self.width < 0 or
     self.height < 0 then
    error ("Grid widgets have to have their width and height set permanently on initiation")
    return
  end

  self.grid = dfhack.penarray.new (self.width, self.height)

  self.viewport = {x1 = 0,
                   x2 = self.frame.r - self.frame.l,
                   y1 = 0,
                   y2 = self.frame.b - self.frame.t}  
end

--================================================================
--  Pans the viewport in the X and Y dimensions the number of steps specified by the parameters.
--  It will stop the panning at 0, however, and will not pan outside of the grid (a grid smaller)
--  than the frame will still have non grid parts in the frame, of course).
--
function Grid:pan (x, y)
  local x_size = self.viewport.x2 - self.viewport.x1 + 1
  local y_size = self.viewport.y2 - self.viewport.y1 + 1

  self.viewport.x1 = self.viewport.x1 + x

  if self.viewport.x1 + x_size > self.width then
    self.viewport.x1 = self.width - x_size
  end

  if self.viewport.x1 < 0 then
    self.viewport.x1 = 0
  end

  self.viewport.x2 = self.viewport.x1 + x_size - 1

  self.viewport.y1 = self.viewport.y1 + y

  if self.viewport.y1 + y_size > self.height then
    self.viewport.y1 = self.height - y_size
  end

  if self.viewport.y1 < 0 then
    self.viewport.y1 = 0
  end

  self.viewport.y2 = self.viewport.y1 + y_size - 1
end

--================================================================
--  Assigns a value to the specified grid (not frame) coordinates. The 'pen'
--  parameter has to be a DFHack 'pen' table or object.
--
function Grid:set (x, y, pen)
  if x < 0 or x >= self.width then
    error ("Grid:set error: x out of bounds " .. tostring (x) .. " vs 0 - " .. tostring (self.width - 1))
    return

  elseif y < 0 or y >= self.height then
    error ("Grid:set error: y out of bounds " .. tostring (y) .. " vs 0 - " .. tostring (self.height - 1))
    return
  end

  self.grid:set_tile (x, y, pen)  
end

--================================================================
--  Returns the data at position x, y in the grid.
--
function Grid:get (x, y)
  if x < 0 or x >= self.width then
    error ("Grid:set error: x out of bounds " .. tostring (x) .. " vs 0 - " .. tostring (self.width - 1))
    return

  elseif y < 0 or y >= self.height then
    error ("Grid:set error: y out of bounds " .. tostring (y) .. " vs 0 - " .. tostring (self.height - 1))
    return
  else
    return self.grid:get_tile (x, y)
  end
end

--================================================================
--  Renders the contents within the viewport into the frame.
--
function Grid:onRenderBody (dc)
  self.grid:draw (self.frame.l,
                  self.frame.t,
                  self.viewport.x2 - self.viewport.x1 + 1,
                  self.viewport.y2 - self.viewport.y1 + 1,
                  self.viewport.x1,
                  self.viewport.y1)
end

Edit: Updated the code with a version of pan that actually seems to work when tested...