diff --git a/Common/LevelWarp.go b/Common/LevelWarp.go new file mode 100644 index 00000000..0a7232bc --- /dev/null +++ b/Common/LevelWarp.go @@ -0,0 +1,49 @@ +package Common + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/ResourcePaths" +) + +type LevelWarpRecord struct { + Id int32 + SelectX int32 + SelectY int32 + SelectDX int32 + SelectDY int32 + ExitWalkX int32 + ExitWalkY int32 + OffsetX int32 + OffsetY int32 + LitVersion bool + Tiles int32 + Direction string +} + +var LevelWarps map[int]*LevelWarpRecord + +func LoadLevelWarps(fileProvider FileProvider) { + LevelWarps = make(map[int]*LevelWarpRecord) + levelWarpData := fileProvider.LoadFile(ResourcePaths.LevelWarp) + streamReader := CreateStreamReader(levelWarpData) + numRecords := int(streamReader.GetInt32()) + for i := 0; i < numRecords; i++ { + id := int(streamReader.GetInt32()) + LevelWarps[id] = &LevelWarpRecord{} + LevelWarps[id].Id = int32(id) + LevelWarps[id].SelectX = streamReader.GetInt32() + LevelWarps[id].SelectY = streamReader.GetInt32() + LevelWarps[id].SelectDX = streamReader.GetInt32() + LevelWarps[id].SelectDY = streamReader.GetInt32() + LevelWarps[id].ExitWalkX = streamReader.GetInt32() + LevelWarps[id].ExitWalkY = streamReader.GetInt32() + LevelWarps[id].OffsetX = streamReader.GetInt32() + LevelWarps[id].OffsetY = streamReader.GetInt32() + LevelWarps[id].LitVersion = streamReader.GetInt32() == 1 + LevelWarps[id].Tiles = streamReader.GetInt32() + LevelWarps[id].Direction = string(streamReader.GetByte()) + streamReader.SkipBytes(3) + } + log.Printf("Loaded %d level warps", len(LevelWarps)) +} diff --git a/Common/ObjectTypes.go b/Common/ObjectTypes.go new file mode 100644 index 00000000..c4494314 --- /dev/null +++ b/Common/ObjectTypes.go @@ -0,0 +1,31 @@ +package Common + +import ( + "log" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/ResourcePaths" +) + +type ObjectTypeRecord struct { + Name string + Token string +} + +var ObjectTypes []ObjectTypeRecord + +func LoadObjectTypes(fileProvider FileProvider) { + objectTypeData := fileProvider.LoadFile(ResourcePaths.ObjectType) + streamReader := CreateStreamReader(objectTypeData) + count := streamReader.GetInt32() + ObjectTypes = make([]ObjectTypeRecord, count) + for i := range ObjectTypes { + nameBytes, _ := streamReader.ReadBytes(32) + tokenBytes, _ := streamReader.ReadBytes(20) + ObjectTypes[i] = ObjectTypeRecord{ + Name: strings.TrimSpace(strings.ReplaceAll(string(nameBytes), string(0), "")), + Token: strings.TrimSpace(strings.ReplaceAll(string(tokenBytes), string(0), "")), + } + } + log.Printf("Loaded %d object types", len(ObjectTypes)) +} diff --git a/Common/Objects.go b/Common/Objects.go new file mode 100644 index 00000000..1fa106db --- /dev/null +++ b/Common/Objects.go @@ -0,0 +1,350 @@ +package Common + +import ( + "log" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/ResourcePaths" +) + +// An ObjectRecord represents the settings for one type of object from objects.txt +type ObjectRecord struct { + Name string + Description string + Id int + Token string // refers to what graphics this object uses + + SpawnMax int // unused? + Selectable [8]bool // is this mode selectable + TrapProbability int // unused + + SizeX int + SizeY int + + NTgtFX int // unknown + NTgtFY int // unknown + NTgtBX int // unknown + NTgtBY int // unknown + + FrameCount [8]int // how many frames does this mode have, 0 = skip + FrameDelta [8]int // what rate is the animation played at (256 = 100% speed) + CycleAnimation [8]bool // probably whether animation loops + LightDiameter [8]int + BlocksLight [8]bool + HasCollision [8]bool + IsAttackable bool // do we kick it when interacting + StartFrame [8]int + + EnvEffect bool // unknown + IsDoor bool + BlockVisibility bool // only works with IsDoor + Orientation int // unknown (1=sw, 2=nw, 3=se, 4=ne) + Trans int // controls palette mapping + + OrderFlag [8]int // 0 = object, 1 = floor, 2 = wall + PreOperate bool // unknown + HasAnimationMode [8]bool // 'Mode' in source, true if this mode is used + + XOffset int // in pixels offset + YOffset int + Draw bool // if false, object isn't drawn (shadow is still drawn and player can still select though) + + LightRed byte // if lightdiameter is set, rgb of the light + LightGreen byte + LightBlue byte + + SelHD bool // whether these DCC components are selectable + SelTR bool + SelLG bool + SelRA bool + SelLA bool + SelRH bool + SelLH bool + SelSH bool + SelS [8]bool + + TotalPieces int // selectable DCC components count + SubClass int // subclass of object: + // 1 = shrine + // 2 = obelisk + // 4 = portal + // 8 = container + // 16 = arcane sanctuary gateway + // 32 = well + // 64 = waypoint + // 128 = secret jails door + + XSpace int // unknown + YSpace int + + NameOffset int // pixels to offset the name from the animation pivot + + MonsterOk bool // unknown + OperateRange int // distance object can be used from, might be unused + ShrineFunction int // unused + Restore bool // if true, object is stored in memory and will be retained if you leave and re-enter the area + + Parm [8]int // unknown + Act int // what acts this object can appear in (15 = all three) + Lockable bool + Gore bool // unknown, something with corpses + Sync bool // unknown + Flicker bool // light flickers if true + Damage int // amount of damage done by this (used depending on operatefn) + Beta bool // if true, appeared in the beta? + Overlay bool // unknown + CollisionSubst bool // unknown, controls some kind of special collision checking? + + Left int // unknown, clickable bounding box? + Top int + Width int + Height int + + OperateFn int // what function is called when the player clicks on the object + // (todo: we should enumerate all the functions somewhere, but probably not here + // b/c it's a very long list) + PopulateFn int // what function is used to spawn this object? + // (see above todo) + InitFn int // what function is run when the object is initialized? + // (see above todo) + ClientFn int // controls special audio-visual functions + // (see above todo) + + RestoreVirgins bool // if true, only restores unused objects (see Restore) + BlockMissile bool // if true, missiles collide with this + DrawUnder bool // if true, drawn as a floor tile is + OpenWarp bool // needs clarification, controls whether highlighting shows + // 'To ...' or 'trap door' when highlighting, not sure which is T/F + AutoMap int // controls how this object appears on the map + // 0 = it doesn't, rest of modes need to be analyzed +} + +// CreateObjectRecord parses a row from objects.txt into an object record +func createObjectRecord(line string) ObjectRecord { + props := strings.Split(line, "\t") + i := -1 + inc := func() int { + i++ + return i + } + result := ObjectRecord{ + Name: props[inc()], + Description: props[inc()], + Id: StringToInt(props[inc()]), + Token: props[inc()], + + SpawnMax: StringToInt(props[inc()]), + Selectable: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + TrapProbability: StringToInt(props[inc()]), + + SizeX: StringToInt(props[inc()]), + SizeY: StringToInt(props[inc()]), + + NTgtFX: StringToInt(props[inc()]), + NTgtFY: StringToInt(props[inc()]), + NTgtBX: StringToInt(props[inc()]), + NTgtBY: StringToInt(props[inc()]), + + FrameCount: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + FrameDelta: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + CycleAnimation: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + LightDiameter: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + BlocksLight: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + HasCollision: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + IsAttackable: StringToUint8(props[inc()]) == 1, + StartFrame: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + + EnvEffect: StringToUint8(props[inc()]) == 1, + IsDoor: StringToUint8(props[inc()]) == 1, + BlockVisibility: StringToUint8(props[inc()]) == 1, + Orientation: StringToInt(props[inc()]), + Trans: StringToInt(props[inc()]), + + OrderFlag: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + PreOperate: StringToUint8(props[inc()]) == 1, + HasAnimationMode: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + + XOffset: StringToInt(props[inc()]), + YOffset: StringToInt(props[inc()]), + Draw: StringToUint8(props[inc()]) == 1, + + LightRed: StringToUint8(props[inc()]), + LightGreen: StringToUint8(props[inc()]), + LightBlue: StringToUint8(props[inc()]), + + SelHD: StringToUint8(props[inc()]) == 1, + SelTR: StringToUint8(props[inc()]) == 1, + SelLG: StringToUint8(props[inc()]) == 1, + SelRA: StringToUint8(props[inc()]) == 1, + SelLA: StringToUint8(props[inc()]) == 1, + SelRH: StringToUint8(props[inc()]) == 1, + SelLH: StringToUint8(props[inc()]) == 1, + SelSH: StringToUint8(props[inc()]) == 1, + SelS: [8]bool{ + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + StringToUint8(props[inc()]) == 1, + }, + + TotalPieces: StringToInt(props[inc()]), + SubClass: StringToInt(props[inc()]), + + XSpace: StringToInt(props[inc()]), + YSpace: StringToInt(props[inc()]), + + NameOffset: StringToInt(props[inc()]), + + MonsterOk: StringToUint8(props[inc()]) == 1, + OperateRange: StringToInt(props[inc()]), + ShrineFunction: StringToInt(props[inc()]), + Restore: StringToUint8(props[inc()]) == 1, + + Parm: [8]int{ + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + StringToInt(props[inc()]), + }, + Act: StringToInt(props[inc()]), + Lockable: StringToUint8(props[inc()]) == 1, + Gore: StringToUint8(props[inc()]) == 1, + Sync: StringToUint8(props[inc()]) == 1, + Flicker: StringToUint8(props[inc()]) == 1, + Damage: StringToInt(props[inc()]), + Beta: StringToUint8(props[inc()]) == 1, + Overlay: StringToUint8(props[inc()]) == 1, + CollisionSubst: StringToUint8(props[inc()]) == 1, + + Left: StringToInt(props[inc()]), + Top: StringToInt(props[inc()]), + Width: StringToInt(props[inc()]), + Height: StringToInt(props[inc()]), + + OperateFn: StringToInt(props[inc()]), + PopulateFn: StringToInt(props[inc()]), + InitFn: StringToInt(props[inc()]), + ClientFn: StringToInt(props[inc()]), + + RestoreVirgins: StringToUint8(props[inc()]) == 1, + BlockMissile: StringToUint8(props[inc()]) == 1, + DrawUnder: StringToUint8(props[inc()]) == 1, + OpenWarp: StringToUint8(props[inc()]) == 1, + + AutoMap: StringToInt(props[inc()]), + } + return result +} + +var Objects map[int]*ObjectRecord + +func LoadObjects(fileProvider FileProvider) { + Objects = make(map[int]*ObjectRecord) + data := strings.Split(string(fileProvider.LoadFile(ResourcePaths.ObjectDetails)), "\r\n")[1:] + for _, line := range data { + if len(line) == 0 { + continue + } + rec := createObjectRecord(line) + Objects[rec.Id] = &rec + } + log.Printf("Loaded %d objects", len(Objects)) +} \ No newline at end of file diff --git a/Core/Engine.go b/Core/Engine.go index 05041933..d4f2698c 100644 --- a/Core/Engine.go +++ b/Core/Engine.go @@ -66,6 +66,9 @@ func CreateEngine() *Engine { Common.LoadTextDictionary(result) Common.LoadLevelTypes(result) Common.LoadLevelPresets(result) + Common.LoadLevelWarps(result) + Common.LoadObjectTypes(result) + Common.LoadObjects(result) Common.LoadSounds(result) result.SoundManager = Sound.CreateManager(result) result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume) diff --git a/OpenDiablo2.exe b/OpenDiablo2.exe new file mode 100644 index 00000000..f35549b4 Binary files /dev/null and b/OpenDiablo2.exe differ diff --git a/ResourcePaths/ResourcePaths.go b/ResourcePaths/ResourcePaths.go index d96b4c5a..5b6ebf4f 100644 --- a/ResourcePaths/ResourcePaths.go +++ b/ResourcePaths/ResourcePaths.go @@ -163,8 +163,10 @@ const ( PatchStringTable = "/data/local/lng/{LANG}/patchstring.tbl" LevelPreset = "/data/global/excel/LvlPrest.bin" LevelType = "/data/global/excel/LvlTypes.bin" + ObjectType = "/data/global/excel/objtype.bin" + LevelWarp = "/data/global/excel/LvlWarp.bin" LevelDetails = "/data/global/excel/Levels.bin" - ObjectDetails = "/data/global/excel/Objects.bin" + ObjectDetails = "/data/global/excel/Objects.txt" SoundSettings = "/data/global/excel/Sounds.txt" // --- Animations ---