deepstrasz
Map Reviewer
- Joined
- Jun 4, 2009
- Messages
- 19,679
There's a difference between the username and the map author. Can you prove you are indeed the author. If not, do you have permission to upload?
Version 6.0 | Version 6.0 | Version 6.0 |
This is my map. I started to make it in Russian, but I had to translate it into English, as many players requested it. Here is the page with her changes. I have all versions of the map. I can send any to you to make sure. Rogue-like Map - WarCraft 3 / Моддинг - XGMThere's a difference between the username and the map author. Can you prove you are indeed the author. If not, do you have permission to upload?
What do I need to do to update the map to version 0.5.10? Re-upload as a separate map, or is it possible to change the map in this post? Unfortunately I can't figure it out.There's a difference between the username and the map author. Can you prove you are indeed the author. If not, do you have permission to upload?
Do not multiupload please. Update like so: Updating resourcesWhat do I need to do to update the map to version 0.5.10? Re-upload as a separate map, or is it possible to change the map in this post? Unfortunately I can't figure it out.
Sorry, but i dont see this button.Do not multiupload please. Update like so: Updating resources
It's there at the bottom of the description as shown in the pictures. Please take some time to find it.Unfortunately, I could not find the file editing interface, although I searched for a long time.
Thank you for your patience! I eventually found where it is.It's there at the bottom of the description as shown in the pictures. Please take some time to find it.
Thanks for the feedback!Been playing with people on my chat and quick suggestions here:
- Make Attribute Bonus (+1, +2, +3 for all) after lvl 10. Helps a lot on late game.
- Save system for your progress RPG style, helps for other runs and lobbies
I've got a save-load system in testing phase, should be good in a few daysAny updates on Save / Load system for Multiplayer?
We're a group of friends just waiting for that :d
Great. Don't like to advertise but you definitely use this map as inspiration: Warrogue_v1.10bYeah, that new dev is me -- played this on battlenet, loved it and asked to help. Map is obviously barebones/WIP, but core gameplay is already fun and addictive, and we've made a lot of progress this week. I see many positive responses about the map, which empowers to keep up the development pace.
Actually, no. Your assumption is wrong. This map was inspired by classic roguelikes such as UnderMine, Hand of Fate 2, Wizard of Legends, Slay the Spire, Dungered, Cult of the Lamb and more.Great. Don't like to advertise but you definitely use this map as inspiration: Warrogue_v1.10b
Great idea! I will add a discord server. I will be glad if you join via the link Join the WC3 - Rogue-like Map Discord Server!Would you perhaps have a Discord? I've got a few ideas, but moderation approval wait time are annoying me :d
I am sorry. I seem to have eaten a word, namely "could". I wanted to mean that the map is good for inspiration not that yours is based on it.Actually, no. Your assumption is wrong.
Thanks for the clarification! References will never be superfluous. Somehow I'll get to this map.I am sorry. I seem to have eaten a word, namely "could". I wanted to mean that the map is good for inspiration not that yours is based on it.
Also, fyi this link is expired nowGreat idea! I will add a discord server. I will be glad if you join via the link Join the WC3 - Rogue-like Map Discord Server!
Thank you! We try.I played this map a bunch yesterday and it's obviously unfinished, some things are not implemented and a bunch of the mercenaries have tooltips missing. It is SO DAMN FUN though! Really excited to see it grow and improve, good job author.
Yes, I screwed up. Here is a perpetual link.Also, fyi this link is expired now
if Debug then Debug.beginFile "Base64" end
--[[
Base64 v1
Provides functionality to tightly pack data, optimized for the most dense info storage.
API:
Base64.Encoder.create() -> Encoder
- Creates a new encoder instance
Encoder:writeBitString(bitString: integer, bitLength: integer)
- Add bitLength bits to the resulting data
Encoder:buildString() -> string
- Returns a string with the encoded data
Base64.Decoder.create(data: string) -> Decoder
- Creates a decoder instance that will read through the string
Decoder:readBitString(bitLength: integer) -> integer
- Reads the next bitLength bits from the
Base64.Internal
CHARMAP - the default RFC4822 compliant charmap
REVERSE_CHARMAP - the inverse of the default charmap
GenerateCharmap(charset: string, voidInt, setSize, i2ch, ch2i) -> Charmap, ReverseCharmap
- Generates a charmap and its reverse for the given charset
- Can be used to obfuscate the encoding
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- 's Base64 @ https://www.hiveworkshop.com/threads/278664/
- Aniki's Base64 & BitBuf @ https://www.hiveworkshop.com/threads/325749/
Updated: 18 Jan 2023
--]]
OnInit("Base64", function()
-- precomputed charmaps
local CHARMAP = {
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
}
local REVERSE_CHARMAP = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
}
--- Generates a new charset dictionaries
---@param charset string
local function GenerateCharmap(charset, voidInt, setSize, i2ch, ch2i)
i2ch = i2ch or {}
ch2i = ch2i or {}
voidInt = voidInt or 1
setSize = setSize or 255
for i = 1, setSize, 1 do
ch2i[i] = voidInt
end
for i = 1, #charset, 1 do
i2ch[i] = charset:sub(i, i)
ch2i[charset:byte(i, i)] = i
end
return i2ch, ch2i
end
local Internal = {
CHARMAP,
REVERSE_CHARMAP,
GenerateCharmap = GenerateCharmap,
}
--[ ENCODER CLASS ]--
--- Encodes a integer value sequence into a string.
--- Supports arbitrary bit lengths.
---@class Encoder
---@field buffer integer
---@field buffer_len integer
---@field bit_len integer
---@field out table
---@field out_len integer
local Encoder = {}
Encoder.__index = Encoder
function Encoder.create()
return setmetatable({
buffer = 0,
buffer_len = 0,
bit_len = 0,
out = {},
out_len = 0
}, Encoder)
end
--- Must be sure that the integer provided is in the range [0, 64]
---@param e Encoder
---@param octet integer
local function writeOctetUnsafe(e, octet)
e.out_len = e.out_len + 1
e.out[e.out_len] = CHARMAP[octet + 1]
end
---@param e Encoder
---@param buffer integer
local function writeBuffer(e, buffer)
writeOctetUnsafe(e, (buffer & 0x3f))
writeOctetUnsafe(e, (buffer & 0xfc0) >> 6)
writeOctetUnsafe(e, (buffer & 0x3f000) >> 12)
writeOctetUnsafe(e, (buffer & 0xfc0000) >> 18)
end
---@param e Encoder
---@param buffer integer
---@param len integer
local function addToBuffer(e, buffer, len)
if len < 24 then
return buffer, len
end
local r, l = 0, 0
if len > 24 then
l = len - 24
r = buffer & ((1 << l) - 1)
buffer = buffer >> l
end
writeBuffer(e, buffer)
return r, l
end
--- Must be sure that the len is in the range [1,31], and the integer is in [0, 2^len - 1]
---@param bstr integer
---@param len integer
function Encoder:writeBitString(bstr, len)
-- optimised for performance
-- bstr = bstr & (1 << len) - 1) -- clamp extra bits for safety -- isn't necessary for Object64 lib
self.bit_len = self.bit_len + len
local shift = self.buffer_len + len
if shift <= 31 then
self.buffer, self.buffer_len = addToBuffer(self, (self.buffer << len) | bstr, shift)
return
end
-- self.buffer <= 23, len <= 31 -> self.buffer + len == 54 in worst case scenario
local rem = shift - 24
writeBuffer(self, (self.buffer << (24 - self.buffer_len)) | (bstr >> rem))
bstr = bstr & ((1 << rem) - 1)
if rem > 24 then
rem = rem - 24
writeBuffer(self, bstr >> rem)
bstr = bstr & ((1 << rem) - 1)
end
self.buffer, self.buffer_len = addToBuffer(self, bstr, rem)
end
function Encoder:buildString()
if self.buffer > 0 then
writeBuffer(self, self.buffer << (24 - self.buffer_len))
end
return table.concat(self.out)
end
--[ DECODER CLASS ]--
---@class Decoder
---@field buffer integer
---@field buffer_len integer
---@field bit_ptr integer
---@field pointer integer
---@field source string
local Decoder = {}
Decoder.__index = Decoder
function Decoder.create(str)
return setmetatable({
buffer = 0,
buffer_len = 0,
bit_ptr = 0,
pointer = 0,
source = str
}, Decoder)
end
---@param e Decoder
local function readOctetUnsafe(e)
if e.pointer >= #e.source then
return 0
end
local value = REVERSE_CHARMAP[string.byte(e.source, e.pointer + 1, e.pointer + 1)]
e.pointer = e.pointer + 1
return value
end
--- Reads the next 24 bits
---@param e Decoder
local function readBuffer(e)
local r = readOctetUnsafe(e)
r = r | readOctetUnsafe(e) << 6
r = r | readOctetUnsafe(e) << 12
r = r | readOctetUnsafe(e) << 18
return r
end
---@param e Decoder
local function readToBuffer(e, buffer, len)
if len > 7 then
return buffer, len
end
return (buffer << 24) | readBuffer(e), len + 24
end
---@param len integer
function Decoder:readBitString(len)
-- optimised for performance
-- reversing the operations in writeBitString
local shift = len - self.buffer_len
if shift < 0 then
shift = -shift
local value = self.buffer >> shift
self.buffer, self.buffer_len = readToBuffer(self, self.buffer & ((1 << shift) - 1), shift)
return value
end
local value = self.buffer << shift
local bstr = readBuffer(self, 0, 0)
if shift > 24 then
shift = shift - 24
value = value | (bstr << shift)
bstr = readBuffer(self, 0, 0)
end
shift = 24 - shift
value = value | (bstr >> shift)
self.buffer, self.buffer_len = readToBuffer(self, bstr & ((1 << shift) - 1), shift)
return value
end
Base64 = {
Encoder = Encoder,
Decoder = Decoder,
Internal = Internal,
}
local function test()
local e = Encoder.create()
e:writeBitString(0x1234567, 28)
e:writeBitString(0x1234567, 28)
e:writeBitString(0xf, 4)
e:writeBitString(0x1234567, 28)
e:writeBitString(0x1234567, 28)
local d = Decoder.create(e:buildString())
d:readBitString(28)
d:readBitString(28)
d:readBitString(4)
d:readBitString(28)
d:readBitString(28)
end
-- Internal.CHARMAP, Internal.REVERSE_CHARMAP = GenerateCharmap(
-- "yJ3uFjRLC0h5NTSMYHm27WOGUI/Q+9rKiDPdagtsABpxlwoce48nkzXqvE1ZbfV6"
-- )
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "FileIO" end
--[[
FileIO v1
Provides functionality to read and write files, optimized with lua functionality in mind.
API:
FileIO.Save(filename, data)
- Write string data to a file
FileIO.Load(filename) -> string
- Read string data from a file
FileIO.SaveAsserted(filename, data, onFail?) -> bool
- Saves the file and checks that it was saved successfully.
If it fails, passes (filename, data, loadResult) to onFail.
FileIO.enabled : bool
- field that indicates that files can be accessed correctly.
Optional requirements:
DebugUtils by Eikonium @ https://www.hiveworkshop.com/threads/330758/
Total Initialization by Bribe @ https://www.hiveworkshop.com/threads/317099/
Inspired by:
- TriggerHappy's Codeless Save and Load @ https://www.hiveworkshop.com/threads/278664/
- ScrewTheTrees's Codeless Save/Sync concept @ https://www.hiveworkshop.com/threads/325749/
Updated: 18 Jan 2023
--]]
OnInit("FileIO", function()
local RAW_PREFIX = ']]i[['
local RAW_SUFFIX = ']]--[['
local RAW_SIZE = 256 - #RAW_PREFIX - #RAW_SUFFIX
local LOAD_ABILITY = FourCC('ANdc')
local function open(filename)
name = filename
PreloadGenClear()
Preload('")\nendfunction\n//!beginusercode\nlocal p={}local i=function(s)table.insert(p,s)end--[[')
end
local function write(s)
for i = 1, #s, RAW_SIZE do
Preload(RAW_PREFIX .. s:sub(i, i + RAW_SIZE - 1) .. RAW_SUFFIX)
end
end
local function close()
Preload(']]BlzSetAbilityTooltip(' ..
LOAD_ABILITY .. ',table.concat(p),0)\n//!endusercode\nfunction a takes nothing returns nothing\n//')
PreloadGenEnd(name)
name = nil
end
---
---@param filename string
---@param data string
local function savefile(filename, data)
open(filename)
write(data)
close()
end
---@param filename string
---@return string?
local function loadfile(filename)
local s = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, '', 0)
Preloader(filename)
local loaded = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
BlzSetAbilityTooltip(LOAD_ABILITY, s, 0)
if loaded == s then
return nil
end
return loaded
end
---@param filename string
---@param data string
---@param onFail function?
---@return boolean
local function saveAsserted(filename, data, onFail)
savefile(filename, data)
local res = loadfile(filename)
if res == data then
return true
end
if onFail then
onFail(filename, data, res)
end
return false
end
local fileIO_enabled = saveAsserted('TestFileIO.pld', 'FileIO is Enabled')
FileIO = {
Save = savefile,
Load = loadfile,
SaveAsserted = saveAsserted,
enabled = fileIO_enabled,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "Object64" end
OnInit("Object64", function(require)
function log2(num)
local i = 0
while num > 0 do
i = i + 1
num = num >> 1
end
return i
end
local Field = {}
--[[ FIELDS: ]]
--[[----------------------]]
--[[ Unsigned Integer ]]
--[[----------------------]]
---@class Field.UInt
---@field name string
---@field bit_len integer
Field.UInt = {}
Field.UInt.__index = Field.UInt
function Field.UInt.new(name, maxValue)
return setmetatable({
name = name,
bit_len = (maxValue and log2(maxValue)) or 31
}, Field.UInt)
end
---@param decoder Decoder
function Field.UInt:decode(decoder)
return decoder:readBitString(self.bit_len)
end
---@param encoder Encoder
function Field.UInt:encode(encoder, value)
encoder:writeBitString(value, self.bit_len)
end
--[[----------------------]]
--[[ Boolean ]]
--[[----------------------]]
---@class Field.Bool
---@field name string
---@field bit_len integer
Field.Bool = {}
Field.Bool.__index = Field.Bool
--- Accepts only dense arrays
---@param name string
function Field.Bool.new(name)
return setmetatable({
name = name,
bit_len = 1
}, Field.Bool)
end
Field.Bool.Instance = Field.Bool.new('bool')
---@param decoder Decoder
function Field.Bool:decode(decoder)
local code = decoder:readBitString(1)
return code ~= 0
end
---@param encoder Encoder
function Field.Bool:encode(encoder, value)
encoder:writeBitString(value * 1, 1)
end
--[[----------------------]]
--[[ Signed Integer ]]
--[[----------------------]]
---@class Field.SignedInt
---@field name string
---@field bit_len integer
Field.SignedInt = {}
Field.SignedInt.__index = Field.SignedInt
function Field.SignedInt.new(name, maxValue)
return setmetatable({
name = name,
bit_len = 1 + ((maxValue and log2(maxValue)) or 31)
}, Field.SignedInt)
end
---@param decoder Decoder
function Field.SignedInt:decode(decoder)
local value = decoder:readBitString(self.bit_len)
if Field.Bool.Instance:decode(decoder) then
return -value
end
return value
end
---@param encoder Encoder
function Field.SignedInt:encode(encoder, value)
if value < 0 then
encoder:writeBitString(-value, self.bit_len)
Field.Bool.Instance:encode(decoder, true)
else
encoder:writeBitString(value, self.bit_len)
Field.Bool.Instance:encode(decoder, false)
end
end
--[[----------------------]]
--[[ Char ]]
--[[----------------------]]
local chars = " !#$%%&'\"()*+,-.0123456789:;=<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}`"
local charset, charset_reverse = Base64.Internal.GenerateCharmap(chars)
-- table.print(charset)
-- table.print(charset_reverse)
---@class Field.Char
---@field name string
---@field bit_len integer
---@field parser ArrayParser
Field.Char = {}
Field.Char.__index = Field.Char
Field.Char.Set = charset
Field.Char.SetRef = charset_reverse
Field.Char.UIntField = Field.UInt.new('char-uint', #chars)
function Field.Char.new(name)
return setmetatable({
name = name,
bit_len = Field.Char.UIntField.bit_len,
}, Field.Char)
end
Field.Char.Instance = Field.Char.new('char')
---@param decoder Decoder
function Field.Char:decode(decoder)
local uint = Field.Char.UIntField:decode(decoder)
return charset[uint + 1] or ' '
end
--- Accepts a byte value, not string
---@param encoder Encoder
function Field.Char:encode(encoder, value)
Field.Char.UIntField:encode(encoder, (charset_reverse[value] or 1) - 1)
end
--[[----------------------]]
--[[ Value Set ]]
--[[----------------------]]
--- Object field info that holds a value from a specific value set.
---@class Field.ValueSet
---@field name string
---@field bit_len integer
---@field ref table
---@field backref table
Field.ValueSet = {}
Field.ValueSet.__index = Field.ValueSet
function Field.ValueSet.createBackRef(object)
backreference = {}
for i, v in ipairs(valueArray) do
backreference[v] = i
end
return backreference
end
--- Accepts only dense arrays
function Field.ValueSet.new(name, valueArray, backreference)
return setmetatable({
name = name,
bit_len = log2(#valueArray),
ref = valueArray,
backref = backreference or Field.ValueSet.createBackRef(valueArray)
}, Field.ValueSet)
end
---@param decoder Decoder
function Field.ValueSet:decode(decoder)
local code = decoder:readBitString(self.bit_len)
return self.ref[code]
end
---@param encoder Encoder
function Field.ValueSet:encode(encoder, value)
local code = self.backref[value]
if not code then
-- return print('Error writing Field ' .. self.name .. ': bad value ' .. value)
end
encoder:writeBitString(code, self.bit_len)
end
-- -- e.g.
-- local InverseBoolField = Field.ValueSet.new('inverse-bool', { true, false })
-- InverseBoolField:encode(..., true) -> 0
-- InverseBoolField:encode(..., false) -> 1
-- InverseBoolField:decode(... => 0) -> true
-- InverseBoolField:decode(... => 1) -> false
--[[----------------------]]
--[[ Array Parser ]]
--[[----------------------]]
--- Array type info specifying value type and maximum capacity.
---@class ArrayParser
---@field minbit_len integer
---@field valueType Field.UInt
---@field length Field.UInt
local ArrayParser = {}
ArrayParser.__index = ArrayParser
function ArrayParser.new(field, maxSize)
local len = Field.UInt.new('length', maxSize)
return setmetatable({
bit_len = len.bit_len + (field.bit_len << len.bit_len),
valueType = field,
length = len
}, ArrayParser)
end
---@param encoder Encoder
function ArrayParser:encode(encoder, array)
-- print('array', self.valueType.name, 'length', #array)
self.length:encode(encoder, #array)
for _, val in ipairs(array) do
-- print('array', self.valueType.name, _, val)
self.valueType:encode(encoder, val)
end
end
---@param decoder Decoder
function ArrayParser:decode(decoder)
local length = self.length:decode(decoder)
-- print('array', self.valueType.name, 'length', length)
local array = {}
for i = 1, length, 1 do
array[i] = self.valueType:decode(decoder)
-- print('array', self.valueType.name, i, array[i])
end
return array
end
--[[----------------------]]
--[[ String ]]
--[[----------------------]]
--- String field info based on the ArrayParser<Char>.
---@class Field.String
---@field name string
---@field bit_len integer
---@field parser ArrayParser
---@field char Field.Char
Field.String = {}
Field.String.__index = Field.String
function Field.String.new(name, maxLen)
local parser = ArrayParser.new(Field.Char.Instance, maxLen)
return setmetatable({
name = name,
bit_len = parser.bit_len,
parser = parser,
}, Field.String)
end
Field.String.Instance = Field.String.new('string-instance')
---@param decoder Decoder
function Field.String:decode(decoder)
local array = self.parser:decode(decoder)
return table.concat(array)
end
---@param encoder Encoder
---@param value string
function Field.String:encode(encoder, value)
-- print('string len', value, #value)
self.parser.length:encode(encoder, #value)
for i = 1, #value, 1 do
-- print('str', value:sub(i, i))
Field.Char.Instance:encode(encoder, value:byte(i, i))
end
end
--[[----------------------]]
--[[ Object ]]
--[[----------------------]]
--- Object field that holds another object.
--- Requires a parser to return the value.
---@class Field.Object
---@field name string
---@field bit_len integer
---@field parser ObjectParser
Field.Object = {}
Field.Object.__index = Field.Object
--- Accepts only a completed parser
function Field.Object.new(name, parser)
return setmetatable({
name = name,
bit_len = parser.bit_len,
parser = parser,
}, Field.Object)
end
---@param decoder Decoder
function Field.Object:decode(decoder)
return self.parser:decode(decoder)
end
---@param encoder Encoder
function Field.Object:encode(encoder, value)
self.parser:encode(encoder, value)
end
--[[----------------------]]
--[[ Object Parser ]]
--[[----------------------]]
--- Object type info specifying what fields to serialize.
---@class ObjectParser
---@field bit_len integer
---@field param table
---@field fieldQueue table
---@field constructor function
---@field postprocessor function
local ObjectParser = {}
ObjectParser.__index = ObjectParser
---@param fieldArray table? Field list
---@param ctor function? Function that creates the object when decoding
---@param postproc function? Function that creates the object when decoding
function ObjectParser.new(fieldArray, ctor, postproc)
local params, queue = {}, nil
if fieldArray then
for _, v in ipairs(fieldArray) do
params[v.name] = v
end
queue = fieldArray
else
queue = {}
end
return setmetatable({
bit_len = 0,
param = params,
fieldQueue = queue,
constructor = ctor or function() return {} end,
postprocessor = postproc or function(x) return x end
}, ObjectParser)
end
function ObjectParser:addField(field)
self.param[field.name] = field
self.fieldQueue[#self.fieldQueue + 1] = field
self.bit_len = self.bit_len + field.bit_len
field.order = #self.fieldQueue
end
function ObjectParser:addFields(...)
local input = table.pack(...)
for i = 1, input.n do
self:addField(input[i])
end
end
---@param encoder Encoder
function ObjectParser:encode(encoder, object)
for _, def in ipairs(self.fieldQueue) do
local value = object[def.name]
-- print('obj[', _, def.name, ']', value)
def:encode(encoder, value)
end
end
---@param decoder Decoder
function ObjectParser:decode(decoder)
local object = self.constructor()
for _, def in ipairs(self.fieldQueue) do
object[def.name] = def:decode(decoder)
-- print('obj[', _, def.name, ']', object[def.name])
end
return self.postprocessor(object)
end
--[[----------------------]]
--[[ Object Switch Parser ]]
--[[----------------------]]
--- Conditional type info that serializes the object into different types
--- depending on a single-type argument.
---@class SwitchParser
---@field argType table
---@field argCreator function
---@field typeDecider function
local SwitchParser = {}
SwitchParser.__index = SwitchParser
---@param argType table The type info of the argument object
---@param argCreator function Function to create the argument from any object
---@param typeDecider function Function to decide the object type by the argument
function SwitchParser.new(argType, argCreator, typeDecider)
return setmetatable({
bit_len = 0,
argType = argType,
argCreator = argCreator,
typeDecider = typeDecider,
}, SwitchParser)
end
---@param encoder Encoder
function SwitchParser:encode(encoder, object)
local arg = self.argCreator(object)
self.argType:encode(encoder, arg)
self.typeDecider(arg):encode(encoder, object)
end
---@param decoder Decoder
function SwitchParser:decode(decoder)
local arg = self.argType:decode(decoder)
return self.typeDecider(arg):decode(decoder)
end
--[[----------------------]]
--[[ Polymorphic Parser ]]
--[[----------------------]]
local PolymorphicParser = {}
PolymorphicParser.__index = PolymorphicParser
---@param argType table The type info of the argument object
---@param classTypes table Array of class types that contain the
---@param typeParsers table Table that matches class type to class parser
function PolymorphicParser.new(argType, classTypes, typeParsers)
local typeField = Field.ValueSet.new('polymorphic-type', classTypes, typeParsers)
return setmetatable(SwitchParser.new(
valueSet,
function(obj) return getmetatable(obj) end,
function(type) return typeParsers[type] end
), PolymorphicParser)
end
setmetatable(PolymorphicParser, SwitchParser)
--[[----------------------]]
--[[ Final lib ]]
--[[----------------------]]
function Serialize(parser, object)
local encoder = Base64.Encoder.create()
parser:encode(encoder, object)
-- print(encoder.bit_len)
return encoder:buildString()
end
function Deserialize(parser, str)
local decoder = Base64.Decoder.create(str)
return parser:decode(decoder)
end
Object64 = {
ObjectParser = ObjectParser,
ArrayParser = ArrayParser,
Field = Field,
Serialize = Serialize,
Deserialize = Deserialize,
}
end)
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "PlayerState" end
OnInit("PlayerState", function()
local localP = GetLocalPlayer()
local localId = GetPlayerId(localP)
local playing = CreateForce()
local playingCount = 0
local multiplayer = false
local onLeaveEvents = {}
local playingInit = {}
local playerName = {}
local tagId = {}
local function onPlayerLeave(func)
onLeaveEvents[#onLeaveEvents + 1] = func
end
local function onPlayingInit(func)
if playingInit then
playingInit[#playingInit + 1] = func
else
ForForce(playing, function() func(GetEnumPlayer()) end)
end
end
do
local leave_trigger = CreateTrigger()
local function playerLeaved()
local p = GetTriggerPlayer()
for i = 1, #onLeaveEvents, 1 do
onLeaveEvents[i](p)
end
end
OnInit.final(function()
TriggerAddCondition(leave_trigger, Condition(playerLeaved))
for i = 0, bj_MAX_PLAYERS - 1 do
local p = Player(i)
local id = GetPlayerId(p)
if GetPlayerController(p) == MAP_CONTROL_USER
and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING then
ForceAddPlayer(playing, p)
playingCount = playingCount + 1
TriggerRegisterPlayerEvent(leave_trigger, p, EVENT_PLAYER_LEAVE)
end
--- BNet tag calculation
local name = GetPlayerName(p)
local bnetName, tag = name:match('([^#]+)#(%%d+)')
if bnetName then
playerName[id] = bnetName
tagId[id] = tonumber(tag)
else
playerName[id] = name
tagId[id] = 0
end
for i = 1, #playingInit, 1 do
playingInit[i](p)
end
end
multiplayer = playingCount > 1
playingInit = nil
end)
onPlayerLeave(function(p)
ForceRemovePlayer(playing, p)
playingCount = playingCount - 1
end)
end
do
local CURRENT_VERSION = 1
local MAP_KEY = "rG!B@n"
local SYNC_PREFIX = "PST"
local pState = {}
local function GetSaveLocation()
return ([[roguelike/%%s/Save-%%d.pld]]):format(playerName[localId], tagId[localId])
end
local playerStateValidation = Object64.ObjectParser.new({
Object64.Field.String.new('key', 6),
Object64.Field.UInt.new('save_version', 10000),
Object64.Field.UInt.new('bnetTag', 10000),
Object64.Field.String.new('player', 32),
}, nil, function(header)
header.valid = header.key == MAP_KEY
and header.player == playerName[localId]
and header.bnetTag == tagId[localId]
and header.save_version <= CURRENT_VERSION
-- print(([[Checking header:
-- key = %%s | ref: %%s
-- player = %%s | ref: %%s
-- bnetTag = %%d | ref: %%d
-- save_version = %%d | ref: %%d
-- -> %%s]]):format(
-- header.key, MAP_KEY,
-- header.player, playerName[localId],
-- header.bnetTag, tagId[localId],
-- header.save_version, CURRENT_VERSION,
-- header.valid
-- ))
return header
end)
local playerStateInfo = Object64.ObjectParser.new({
Object64.Field.String.new('key', 6),
Object64.Field.UInt.new('save_version', 10000),
Object64.Field.UInt.new('bnetTag', 10000),
Object64.Field.String.new('player', 32),
Object64.Field.Object.new('data', Object64.ObjectParser.new({
Object64.Field.UInt.new('game_count', 8192),
Object64.Field.UInt.new('lumber'),
Object64.Field.UInt.new('souls', 3),
Object64.Field.UInt.new('longevity', 256),
Object64.Field.UInt.new('merc_dmg', 256),
Object64.Field.UInt.new('merc_hp', 256),
Object64.Field.UInt.new('start_gold', 256),
Object64.Field.UInt.new('start_exp', 256),
})),
})
local function CreatePlayerData()
return {
game_count = 1,
run_count = 0,
longevity = 0,
merc_dmg = 0,
merc_hp = 0,
start_gold = 0,
start_exp = 0,
souls = 0,
}
end
local function OnLoadPlayerState(p, data)
local id = GetPlayerId(p)
-- print(("Received %%s <- %%s"):format(data, playerName[id]))
local newState = Object64.Deserialize(playerStateInfo, data)
local data = newState.data
pState[id].data = data
data.game_count = data.game_count + 1
SetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER,
GetPlayerState(p, PLAYER_STATE_RESOURCE_LUMBER)
+ data.lumber)
data.lumber = nil
print(("Welcome back to your %%dth game, %%s"):format(data.game_count, playerName[id]))
if not multiplayer then
print("You are playing in sigleplayer; your progress will not be saved.")
end
for i = 1, data.souls do
GroupAddUnit(
udg_select_hero_souls,
CreateUnit(p, FourCC("ewsp"),
GetRandomReal(GetRectMinX(gg_rct_selectHero2_Copy), GetRectMaxX(gg_rct_selectHero2_Copy)),
GetRandomReal(GetRectMinY(gg_rct_selectHero2_Copy), GetRectMaxY(gg_rct_selectHero2_Copy)),
bj_UNIT_FACING)
)
end
-- table.print(PlayerState[id])
end
local syncTrigger = CreateTrigger()
TriggerAddCondition(syncTrigger, Condition(function()
OnLoadPlayerState(GetTriggerPlayer(), BlzGetTriggerSyncData())
end))
local function SavePlayerState()
if not multiplayer then return end
local state = pState[localId]
state.key = MAP_KEY
state.data.lumber = GetPlayerState(localP, PLAYER_STATE_RESOURCE_LUMBER)
local data = Object64.Serialize(playerStateInfo, state)
state.key = nil
state.data.lumber = nil
FileIO.Save(GetSaveLocation(), data)
-- print("Your soul shall be remembered for ages...")
-- print(("Saved %%s -> %%s"):format(data, GetSaveLocation()))
end
local function LoadPlayerState(p)
if localP == p then
local data = FileIO.Load(GetSaveLocation())
if not data then return end
-- print(("Sent %%s <- %%s"):format(data, GetSaveLocation()))
local h = Object64.Deserialize(playerStateValidation, data)
if h.valid then
BlzSendSyncData(SYNC_PREFIX, data)
else
-- if h.key ~= MAP_KEY then
-- Debug.throwError(('Invalid PlayerState.'))
-- end
-- if h.save_version > CURRENT_VERSION then
-- Debug.throwError(('Your Current PlayerState was saved on a higher map version.'))
-- end
-- if h.player ~= playerName[localId] then
-- Debug.throwError(('This PlayerState belongs to another player!'))
-- end
-- if h.bnetTag ~= playerName[localId] and h.bnetTag ~= 0 then
-- Debug.throwError(('PlayerState belongs to another battle.net account!'))
-- end
end
end
end
onPlayingInit(function(p)
local id = GetPlayerId(p)
pState[id] = {
save_version = CURRENT_VERSION,
bnetTag = tagId[id],
player = playerName[id],
data = CreatePlayerData()
}
BlzTriggerRegisterPlayerSyncEvent(syncTrigger, p, SYNC_PREFIX, false)
LoadPlayerState(p)
end)
PlayerState = {
Save = SavePlayerState,
-- Load = LoadPlayerState,
}
function PlayerState.OnNewRun()
ForForce(playing, function()
local data = pState[GetPlayerId(GetEnumPlayer())].data
data.run_count = data.run_count + 1
end)
end
GlobalRemapArray("udg_souls_upg_gold",
function(i) return pState[i - 1].data.start_gold or 0 end,
function(i, value) pState[i - 1].data.start_gold = value end
)
GlobalRemapArray("udg_souls_upg_xp",
function(i) return pState[i - 1].data.start_xp or 0 end,
function(i, value) pState[i - 1].data.start_xp = value end
)
GlobalRemapArray("udg_souls_upg_steps",
function(i) return pState[i - 1].data.longevity or 0 end,
function(i, value) pState[i - 1].data.longevity = value end
)
GlobalRemapArray("udg_souls_upg_mercDamage",
function(i) return pState[i - 1].data.merc_dmg or 0 end,
function(i, value) pState[i - 1].data.merc_dmg = value end
)
GlobalRemapArray("udg_souls_upg_mercHP",
function(i) return pState[i - 1].data.merc_hp or 0 end,
function(i, value) pState[i - 1].data.merc_hp = value end
)
GlobalRemapArray("udg_souls_upg_heroes",
function(i) return pState[i - 1].data.souls or 0 end,
function(i, value) pState[i - 1].data.souls = value end
)
end
end)
if Debug then Debug.endFile() end
I really like the map, but i think that the difficulty spike between acts is way to massive. 10 times stronger is an insanely big jump, and the game becomes impossible when reaching act 3 (since mobs deal x100 damage and take 1% damage). Upgraded skills also suck bigtime, their mana cost goes up way too much and the damage doesnt keep up with how powerful the mobs get. Also, it s really confusing that the mobs have the same stats and deal tons of damage while taking none, seeing the number would be better i believe. Exponential difficulty adjustments are very hard to balance, but i figure that mobs being 3 or 5 times stronger between acts would be a bit more balanced (upon reaching act 3 a 50 level hero cant even damage monsters and it takes multiple hits with whosyourdaddy active to kill them)
Your're welcome. Good luck working on the second version, I can't wait to play it !Thanks for your feedback!This really helps me a lot in terms of motivation.Now I'm developing the second version of the map, which has little in common with the first.I'm not sure if it's worth spending time polishing the current map. Or is it better to focus on the second version.In any case, thanks again for the motivation! It's important for me.
Thanks for your feedback! I agree with almost everything written here. You just need to do it. And do it carefully so that some aspects of the game do not destroy others.There are a lot of ideas, not enough hands, but I try -) Thanks again!Hey played this game for an hour or so and I was really blown
Thanks for the review and the game! Later I will add a video to the zero post -)Played it for myself, I like that you can continue afterward with saved progress.
For myself, I think that map should have more custom spells or revamped abilities for heroes, cause I see that the core idea is in a very good state.
Anyway, I played it again and enjoyed it.