diff --git a/.github/workflows/action.md b/.github/workflows/action.md new file mode 100644 index 0000000..802053c --- /dev/null +++ b/.github/workflows/action.md @@ -0,0 +1,19 @@ +# Discord Webhook Action + +These are some github actions I use on my packs. + +Feel free to use as you see fit. + +## discord_webhook.yml + +This is the discord webhook action I use to automatically post updates of my pack to discord. + +Requires the Webhook URL to be placed in a repo secret on github named ``DISCORD_WEBHOOK_URL``. + +## validation.yml + +This is the pack validation action I use to automatically validate json against the offical PopTracker schemas. + +It uses the [PopTracker pack-checker-action](https://github.com/PopTracker/pack-checker-action). + +Triggers whenever a push or pull request including changes to any json file are made. Can also be run manually. \ No newline at end of file diff --git a/.github/workflows/discord_webhook.yml b/.github/workflows/discord_webhook.yml new file mode 100644 index 0000000..b9f659f --- /dev/null +++ b/.github/workflows/discord_webhook.yml @@ -0,0 +1,14 @@ +name: Discord webhook +on: + release: + types: [published] + +jobs: + discord_webhook: + runs-on: ubuntu-latest + steps: + - name: Discord Webhook Notify + uses: Cyb3RGER/discord-webhook-notify@master + with: + webhookUrl: ${{ secrets.DISCORD_WEBHOOK_URL }} + severity: info diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 0000000..2feb286 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,21 @@ +name: Pack validation +on: + workflow_dispatch: + push: + paths: + - '**.json' + pull_request: + paths: + - '**.json' +jobs: + validation: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.6.0 + - name: Validate pack (lax) + uses: PopTracker/pack-checker-action@v1 + - name: Validate pack (strict) + uses: PopTracker/pack-checker-action@v1 + with: + strict: true + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a464c8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +todo.* +.~lock.* +*.xcf \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..8e94a79 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Template Pack for PopTracker + +This a template tracker package for PopTracker. + +Includes examples for AP and SNES memory autotracking and beginner-friendly explanations in the pack. + +## Installation + +Just download the lastest build or source and put in your packs folder (unpacked). + +## More Info + +Check out PopTrackers Documentation on packs [here](https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md) + +Still having trouble realizing your pack and looking for help or just want more information about everything PopTracker? Check out the ['Unofficial' PopTracker Discord Server](https://discord.com/invite/gwThqMCPgK)! + +## License + +Feel free to use this template without credit for all your PopTracker packs! \ No newline at end of file diff --git a/images/items/consumable.png b/images/items/consumable.png new file mode 100644 index 0000000..d601037 Binary files /dev/null and b/images/items/consumable.png differ diff --git a/images/items/progressive.png b/images/items/progressive.png new file mode 100644 index 0000000..6dc724f Binary files /dev/null and b/images/items/progressive.png differ diff --git a/images/items/progressive_2.png b/images/items/progressive_2.png new file mode 100644 index 0000000..26a338d Binary files /dev/null and b/images/items/progressive_2.png differ diff --git a/images/items/toggle.png b/images/items/toggle.png new file mode 100644 index 0000000..8d78a56 Binary files /dev/null and b/images/items/toggle.png differ diff --git a/images/maps/map.png b/images/maps/map.png new file mode 100644 index 0000000..2f69d34 Binary files /dev/null and b/images/maps/map.png differ diff --git a/images/maps/map_2.png b/images/maps/map_2.png new file mode 100644 index 0000000..2f69d34 Binary files /dev/null and b/images/maps/map_2.png differ diff --git a/items/items.json b/items/items.json new file mode 100644 index 0000000..3cb31ef --- /dev/null +++ b/items/items.json @@ -0,0 +1,56 @@ +//more info: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#items +[ + { + "name": "Toggle", + "type": "toggle", + "img": "images/items/toggle.png", + "codes": "toggle" + }, + { + "name": "Progressive", + "type": "progressive", + "allow_disabled": true, + "initial_stage_idx": 0, + "stages": [ + { + "img": "images/items/progressive.png", + "codes": "progressive", + "inherit_codes": false + }, + { + "img": "images/items/progressive_2.png", + "codes": "progressive_2", + "inherit_codes": false + } + ] + }, + { + "name": "Consumable", + "type": "consumable", + "img": "images/items/consumable.png", + "max_quantity": 8, + "increment": 1, + "decrement": 1, + "initial_quantity": 1, + "overlay_background": "#000000", + "overlay_font_size": 8, + "codes": "consumable" + }, + { + "name": "Progressive Toggle", + "type": "progressive_toggle", + "initial_stage_idx": 0, + "stages": [ + { + "img": "images/items/progressive.png", + "codes": "progressive_toggle", + "inherit_codes": false + }, + { + "img": "images/items/progressive_2.png", + "codes": "progressive_toggle_2", + "inherit_codes": false + } + ] + } +] \ No newline at end of file diff --git a/layouts/broadcast.json b/layouts/broadcast.json new file mode 100644 index 0000000..ef98b2a --- /dev/null +++ b/layouts/broadcast.json @@ -0,0 +1,21 @@ +//sets the layout for the broadcast view +{ + "tracker_broadcast": { + "type": "array", + "orientation": "vertical", + "margin": "0,0", + "content": [ + { + "type": "array", + "orientation": "horizontal", + "margin": "0,0", + "content": [ + { + "type": "layout", + "key": "shared_item_grid" + } + ] + } + ] + } +} \ No newline at end of file diff --git a/layouts/items.json b/layouts/items.json new file mode 100644 index 0000000..446703d --- /dev/null +++ b/layouts/items.json @@ -0,0 +1,33 @@ +// sets layouts for shadred items grids +// bigger example: https://github.com/Cyb3RGER/sm_ap_tracker/blob/main/layouts/items.json +{ + "shared_item_grid": { + "type": "array", + "orientation": "vertical", + "margin": "0,0", + "content": [ + { + "type": "array", + "orientation": "horizontal", + "margin": "0,0", + "content": [ + { + "type": "itemgrid", + "item_margin": "10, 10", + "h_alignment": "left", + "rows": [ + [ + "toggle", + "progressive" + ], + [ + "consumable", + "progressive_toggle" + ] + ] + } + ] + } + ] + } +} \ No newline at end of file diff --git a/layouts/tracker.json b/layouts/tracker.json new file mode 100644 index 0000000..8b97176 --- /dev/null +++ b/layouts/tracker.json @@ -0,0 +1,104 @@ +//sets the trackers layout +//more info: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#layouts +{ + "tracker_default": { + "type": "container", + "background": "#000000", + "content": { + "type": "dock", + "dropshadow": true, + "content": [ + { + "type": "dock", + "dock": "left", + "v_alignment": "stretch", + "content": [ + { + "type": "group", + "header": "Items", + "dock": "top", + "content": { + "type": "layout", + "key": "shared_item_grid" + } + } + ] + }, + { + "type": "tabbed", + "tabs": [ + { + "title": "Map 1", + "content": { + "type": "map", + "maps": [ + "map1" + ] + } + }, + { + "title": "Map 2", + "content": { + "type": "map", + "maps": [ + "map2" + ] + } + } + ] + } + ] + } + }, + "tracker_horizontal": { + "type": "container", + "background": "#000000", + "content": { + "type": "dock", + "dropshadow": true, + "content": [ + { + "type": "dock", + "dock": "bottom", + "content": [ + { + "type": "group", + "header": "Items", + "dock": "left", + "margin": "0,0,3,0", + "content": { + "type": "layout", + "h_alignment": "center", + "v_alignment": "center", + "key": "shared_item_grid" + } + } + ] + }, + { + "type": "tabbed", + "tabs": [ + { + "title": "Map 1", + "content": { + "type": "map", + "maps": [ + "map1" + ] + } + }, + { + "title": "Map 2", + "content": { + "type": "map", + "maps": [ + "map2" + ] + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/locations/locations.json b/locations/locations.json new file mode 100644 index 0000000..e9c1e99 --- /dev/null +++ b/locations/locations.json @@ -0,0 +1,57 @@ +[ + { + "name": "Example Parent", + "children": [ + { + "name": "Example Location 1", + "access_rules": [ + //read as: progressive_2 or toggle + "progressive_2", + "toggle" + ], + "sections": [ + { + "name": "Example Section 1", + "item_count": 1 + }, + { + "name": "Example Section 2", + "item_count": 3 + } + ], + "map_locations": [ + { + "map": "map1", + "x": 1750, + "y": 700 + }, + { + "map": "map2", + "x": 1000, + "y": 500 + } + ] + }, + { + "name": "Example Location 2", + "access_rules": [ + //read as: progressive_toggle_2 and has more then 4 consumable + "progressive_toggle_2, $has_more_then_n_consumable|4" + ], + "sections": [ + { + "name": "Example Section 1", + "item_count": 1 + } + ], + "map_locations": [ + { + "map": "map1", + "x": 1500, + "y": 400 + } + ] + } + ] + } +] \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..0ca2163 --- /dev/null +++ b/manifest.json @@ -0,0 +1,25 @@ +// this the manifest for you pack +// it contains basic information about your pack +// for more info on the manifest check: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#manifest +{ + "name": "Template Pack for PopTracker", + "game_name": "", + "package_version": "1.0.0.0", + "package_uid": "template_pack", + "platform": "snes", //enables SNES memory autotracking interface - remove/change if not needed + "author": "Cyb3R", + "variants": { + "standard": { + "display_name": "Map Tracker", + "flags": [ + "ap" //enables AP autotracking interface - remove if not needed + ] + }, + "var_itemsonly": { + "display_name": "Items Only", + "flags": [ + "ap" //enables AP autotracking interface - remove if not needed + ] + } + } +} \ No newline at end of file diff --git a/maps/maps.json b/maps/maps.json new file mode 100644 index 0000000..bd9c388 --- /dev/null +++ b/maps/maps.json @@ -0,0 +1,15 @@ +//more info: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#maps +[ + { + "name": "map1", + "location_size": 48, + "location_border_thickness": 4, + "img": "images/maps/map.png" + }, + { + "name": "map2", + "location_size": 48, + "location_border_thickness": 4, + "img": "images/maps/map_2.png" + } +] \ No newline at end of file diff --git a/scripts/autotracking.lua b/scripts/autotracking.lua new file mode 100644 index 0000000..8030319 --- /dev/null +++ b/scripts/autotracking.lua @@ -0,0 +1,25 @@ +-- Configuration -------------------------------------- +AUTOTRACKER_ENABLE_ITEM_TRACKING = true +AUTOTRACKER_ENABLE_LOCATION_TRACKING = true and not IS_ITEMS_ONLY +AUTOTRACKER_ENABLE_DEBUG_LOGGING = true and ENABLE_DEBUG_LOG +AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP = true and AUTOTRACKER_ENABLE_DEBUG_LOGGING +AUTOTRACKER_ENABLE_DEBUG_LOGGING_SNES = true and AUTOTRACKER_ENABLE_DEBUG_LOGGING +------------------------------------------------------- +print("") +print("Active Auto-Tracker Configuration") +print("---------------------------------------------------------------------") +print("Enable Item Tracking: ", AUTOTRACKER_ENABLE_ITEM_TRACKING) +print("Enable Location Tracking: ", AUTOTRACKER_ENABLE_LOCATION_TRACKING) +if AUTOTRACKER_ENABLE_DEBUG_LOGGING then + print("Enable Debug Logging: ", AUTOTRACKER_ENABLE_DEBUG_LOGGING) + print("Enable AP Debug Logging: ", AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP) + print("Enable SNES Debug Logging: ", AUTOTRACKER_ENABLE_DEBUG_LOGGING_SNES) +end +print("---------------------------------------------------------------------") +print("") + +-- loads the AP autotracking code +ScriptHost:LoadScript("scripts/autotracking/archipelago.lua") +-- loads the SNES autotracking code +ScriptHost:LoadScript("scripts/autotracking/snes.lua") + diff --git a/scripts/autotracking/archipelago.lua b/scripts/autotracking/archipelago.lua new file mode 100644 index 0000000..c7ee6d4 --- /dev/null +++ b/scripts/autotracking/archipelago.lua @@ -0,0 +1,190 @@ +-- this is an example/ default implementation for AP autotracking +-- it will use the mappings defined in item_mapping.lua and location_mapping.lua to track items and locations via thier ids +-- it will also load the AP slot data in the global SLOT_DATA, keep track of the current index of on_item messages in CUR_INDEX +-- addition it will keep track of what items are local items and which one are remote using the globals LOCAL_ITEMS and GLOBAL_ITEMS +-- this is useful since remote items will not reset but local items might +ScriptHost:LoadScript("scripts/autotracking/item_mapping.lua") +ScriptHost:LoadScript("scripts/autotracking/location_mapping.lua") + +CUR_INDEX = -1 +SLOT_DATA = nil +LOCAL_ITEMS = {} +GLOBAL_ITEMS = {} + +function onClear(slot_data) + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("called onClear, slot_data:\n%s", dump_table(slot_data))) + end + SLOT_DATA = slot_data + CUR_INDEX = -1 + -- reset locations + for _, v in pairs(LOCATION_MAPPING) do + if v[1] then + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onClear: clearing location %s", v[1])) + end + local obj = Tracker:FindObjectForCode(v[1]) + if obj then + if v[1]:sub(1, 1) == "@" then + obj.AvailableChestCount = obj.ChestCount + else + obj.Active = false + end + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onClear: could not find object for code %s", v[1])) + end + end + end + -- reset items + for _, v in pairs(ITEM_MAPPING) do + if v[1] and v[2] then + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onClear: clearing item %s of type %s", v[1], v[2])) + end + local obj = Tracker:FindObjectForCode(v[1]) + if obj then + if v[2] == "toggle" then + obj.Active = false + elseif v[2] == "progressive" then + obj.CurrentStage = 0 + obj.Active = false + elseif v[2] == "consumable" then + obj.AcquiredCount = 0 + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onClear: unknown item type %s for code %s", v[2], v[1])) + end + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onClear: could not find object for code %s", v[1])) + end + end + end + LOCAL_ITEMS = {} + GLOBAL_ITEMS = {} + -- manually run snes interface functions after onClear in case we are already ingame + if PopVersion < "0.20.1" or AutoTracker:GetConnectionState("SNES") == 3 then + -- add snes interface functions here + end +end + +-- called when an item gets collected +function onItem(index, item_id, item_name, player_number) + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("called onItem: %s, %s, %s, %s, %s", index, item_id, item_name, player_number, CUR_INDEX)) + end + if not AUTOTRACKER_ENABLE_ITEM_TRACKING then + return + end + if index <= CUR_INDEX then + return + end + local is_local = player_number == Archipelago.PlayerNumber + CUR_INDEX = index; + local v = ITEM_MAPPING[item_id] + if not v then + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onItem: could not find item mapping for id %s", item_id)) + end + return + end + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onItem: code: %s, type %s", v[1], v[2])) + end + if not v[1] then + return + end + local obj = Tracker:FindObjectForCode(v[1]) + if obj then + if v[2] == "toggle" then + obj.Active = true + elseif v[2] == "progressive" then + if obj.Active then + obj.CurrentStage = obj.CurrentStage + 1 + else + obj.Active = true + end + elseif v[2] == "consumable" then + obj.AcquiredCount = obj.AcquiredCount + obj.Increment + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onItem: unknown item type %s for code %s", v[2], v[1])) + end + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onItem: could not find object for code %s", v[1])) + end + -- track local items via snes interface + if is_local then + if LOCAL_ITEMS[v[1]] then + LOCAL_ITEMS[v[1]] = LOCAL_ITEMS[v[1]] + 1 + else + LOCAL_ITEMS[v[1]] = 1 + end + else + if GLOBAL_ITEMS[v[1]] then + GLOBAL_ITEMS[v[1]] = GLOBAL_ITEMS[v[1]] + 1 + else + GLOBAL_ITEMS[v[1]] = 1 + end + end + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("local items: %s", dump_table(LOCAL_ITEMS))) + print(string.format("global items: %s", dump_table(GLOBAL_ITEMS))) + end + if PopVersion < "0.20.1" or AutoTracker:GetConnectionState("SNES") == 3 then + -- add snes interface functions here for local item tracking + end +end + +-- called when a location gets cleared +function onLocation(location_id, location_name) + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("called onLocation: %s, %s", location_id, location_name)) + end + if not AUTOTRACKER_ENABLE_LOCATION_TRACKING then + return + end + local v = LOCATION_MAPPING[location_id] + if not v and AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onLocation: could not find location mapping for id %s", location_id)) + end + if not v[1] then + return + end + local obj = Tracker:FindObjectForCode(v[1]) + if obj then + if v[1]:sub(1, 1) == "@" then + obj.AvailableChestCount = obj.AvailableChestCount - 1 + else + obj.Active = true + end + elseif AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("onLocation: could not find object for code %s", v[1])) + end +end + +-- called when a locations is scouted +function onScout(location_id, location_name, item_id, item_name, item_player) + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("called onScout: %s, %s, %s, %s, %s", location_id, location_name, item_id, item_name, + item_player)) + end + -- not implemented yet :( +end + +-- called when a bounce message is received +function onBounce(json) + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_AP then + print(string.format("called onBounce: %s", dump_table(json))) + end + -- your code goes here +end + +-- add AP callbacks +-- un-/comment as needed +Archipelago:AddClearHandler("clear handler", onClear) +if AUTOTRACKER_ENABLE_ITEM_TRACKING then + Archipelago:AddItemHandler("item handler", onItem) +end +if AUTOTRACKER_ENABLE_LOCATION_TRACKING then + Archipelago:AddLocationHandler("location handler", onLocation) +end +-- Archipelago:AddScoutHandler("scout handler", onScout) +-- Archipelago:AddBouncedHandler("bounce handler", onBounce) diff --git a/scripts/autotracking/autotracking.md b/scripts/autotracking/autotracking.md new file mode 100644 index 0000000..a169683 --- /dev/null +++ b/scripts/autotracking/autotracking.md @@ -0,0 +1,7 @@ +# Autotracking + +PopTracker implements multiple autotracking interfaces (currently SNES, AP and UAT). + +This example currently shows off the SNES and AP interfaces. + +For more info check here: https://github.com/black-sliver/PopTracker/blob/master/doc/AUTOTRACKING.md#poptracker-auto-tracking \ No newline at end of file diff --git a/scripts/autotracking/item_mapping.lua b/scripts/autotracking/item_mapping.lua new file mode 100644 index 0000000..d0ee93c --- /dev/null +++ b/scripts/autotracking/item_mapping.lua @@ -0,0 +1,9 @@ +-- use this file to map the AP item ids to your items +-- first value is the code of the target item and the second is the item type (currently only "toggle", "progressive" and "consumable" but feel free to expand for your needs!) +-- here are the SM items as an example: https://github.com/Cyb3RGER/sm_ap_tracker/blob/main/scripts/autotracking/item_mapping.lua +ITEM_MAPPING = { + [00000] = {"toggle", "toggle"}, + [00001] = {"progressive", "progressive"}, + [00002] = {"consumable", "consumable"}, + [00003] = {"progressive_toggle", "progressive"} -- progressive_toggle should work with the progressive type but might need additional code to work for your needs +} \ No newline at end of file diff --git a/scripts/autotracking/location_mapping.lua b/scripts/autotracking/location_mapping.lua new file mode 100644 index 0000000..f550d8e --- /dev/null +++ b/scripts/autotracking/location_mapping.lua @@ -0,0 +1,8 @@ +-- use this file to map the AP location ids to your locations +-- to reference a location in Pop use @ in the beginning and then path to the section (more info: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#locations) +-- to reference an item use it's code +-- here are the SM locations as an example: https://github.com/Cyb3RGER/sm_ap_tracker/blob/main/scripts/autotracking/location_mapping.lua +LOCATION_MAPPING = { + [00001] = {"@Example Parent/Example Location 1/Example Section 1"}, + [00002] = {"toggle"}, +} diff --git a/scripts/autotracking/snes.lua b/scripts/autotracking/snes.lua new file mode 100644 index 0000000..755fbd3 --- /dev/null +++ b/scripts/autotracking/snes.lua @@ -0,0 +1,29 @@ +-- this is an example file for SNES memory autotracking +-- more info: https://github.com/black-sliver/PopTracker/blob/master/doc/AUTOTRACKING.md#memory-interface-usb2snes +EXAMPLE_ADDR = 0x7E07F7 +EXAMPLE_SIZE = 0x8 + +function update_example(segment) + if not AUTOTRACKER_ENABLE_ITEM_TRACKING then + return + end + local readResult = segment:ReadUInt8(EXAMPLE_ADDR) -- prefered way of reading + local readResult2 = AutoTracker:ReadU16(EXAMPLE_ADDR + 0x1) -- alternative way of reading + if AUTOTRACKER_ENABLE_DEBUG_LOGGING_SNES then + print(string.format("update_example: readResult: %x, readResult2: %x", readResult, readResult2)) + end + -- changing example item + local obj = Tracker:FindObjectForCode("toggle") + if obj then + obj.Active = readResult2 > readResult + end + -- changing example section + local loc = Tracker:FindObjectForCode("@Example Parent/Example Location 1/Example Section 1") + if loc and readResult2 > readResult then + loc.AvailableChestCount = loc.AvailableChestCount - 1 + end +end + +if AUTOTRACKER_ENABLE_ITEM_TRACKING then + ScriptHost:AddMemoryWatch('example', EXAMPLE_ADDR, EXAMPLE_SIZE, update_example) +end diff --git a/scripts/custom_items/class.lua b/scripts/custom_items/class.lua new file mode 100644 index 0000000..1b1c992 --- /dev/null +++ b/scripts/custom_items/class.lua @@ -0,0 +1,113 @@ +-- This file is sourced from https://github.com/jonstoler/class.lua, and is licensed freely for any purpose, given thanks. Thank you. +-- License from https://github.com/jonstoler/class.lua/blob/master/LICENSE.md as of retrieving this source on 4/8/2019 + +-- By attaching this document to the given files (the “work”), you, the licensee, are hereby granted free usage in both personal and +-- commerical environments, without any obligation of attribution or payment (monetary or otherwise). The licensee is free to use, copy, +-- modify, publish, distribute, sublicence, and/or merchandise the work, subject to the licensee inflecting a positive message unto someone. +-- This includes (but is not limited to): smiling, being nice, saying “thank you”, assisting other persons, or any similar actions percolating the given concept. + +-- The above copyright notice serves as a permissions notice also, and may optionally be included in copies or portions of the work. + +-- The work is provided “as is”, without warranty or support, express or implied. The author(s) are not liable for any damages, misuse, or other claim, whether from or as a consequence of usage of the given work. + +Class = {} + +-- default (empty) constructor +function Class:init(...) end + +-- create a subclass +function Class:extend(obj) + local obj = obj or {} + + local function copyTable(table, destination) + local table = table or {} + local result = destination or {} + + for k, v in pairs(table) do + if not result[k] then + if type(v) == "table" and k ~= "__index" and k ~= "__newindex" then + result[k] = copyTable(v) + else + result[k] = v + end + end + end + + return result + end + + copyTable(self, obj) + + obj._ = obj._ or {} + + local mt = {} + + -- create new objects directly, like o = Object() + mt.__call = function(self, ...) + return self:new(...) + end + + -- allow for getters and setters + mt.__index = function(table, key) + local val = rawget(table._, key) + if val and type(val) == "table" and (val.get ~= nil or val.value ~= nil) then + if val.get then + if type(val.get) == "function" then + return val.get(table, val.value) + else + return val.get + end + elseif val.value then + return val.value + end + else + return val + end + end + + mt.__newindex = function(table, key, value) + local val = rawget(table._, key) + if val and type(val) == "table" and ((val.set ~= nil and val._ == nil) or val.value ~= nil) then + local v = value + if val.set then + if type(val.set) == "function" then + v = val.set(table, value, val.value) + else + v = val.set + end + end + val.value = v + if val and val.afterSet then val.afterSet(table, v) end + else + table._[key] = value + end + end + + setmetatable(obj, mt) + + return obj +end + +-- set properties outside the constructor or other functions +function Class:set(prop, value) + if not value and type(prop) == "table" then + for k, v in pairs(prop) do + rawset(self._, k, v) + end + else + rawset(self._, prop, value) + end +end + +-- create an instance of an object with constructor parameters +function Class:new(...) + local obj = self:extend({}) + if obj.init then obj:init(...) end + return obj +end + + +function class(attr) + attr = attr or {} + return Class:extend(attr) +end \ No newline at end of file diff --git a/scripts/custom_items/custom_items.md b/scripts/custom_items/custom_items.md new file mode 100644 index 0000000..65c0fef --- /dev/null +++ b/scripts/custom_items/custom_items.md @@ -0,0 +1,13 @@ +# Custom items via Lua API + +This custom item is using an example from the [Evermizer tracker pack](https://github.com/Cyb3RGER/evermizer-tracker-package) + +It creates and extend version of a ``progressive_toggle`` item + +``class.lua`` allows you to create "classes" in lua + +``progressiveTogglePlusWrapper.lua`` creates the wrapper class that has all need funtion to communicate with PopTracker + +``progressiveTogglePlus.lua`` create the actuall class + +more info soon \ No newline at end of file diff --git a/scripts/custom_items/progressiveTogglePlus.lua b/scripts/custom_items/progressiveTogglePlus.lua new file mode 100644 index 0000000..7099770 --- /dev/null +++ b/scripts/custom_items/progressiveTogglePlus.lua @@ -0,0 +1,258 @@ +ProgressiveTogglePlus = class(CustomItemProgressiveTogglePlus) + +--[[ + states : table + { + [state : integer] = --0-based index + { + image : string --path to the image + codes : string --codes for the state + disabled : boolean --disabled + } + ... + } + -- states for the progression + + loop : boolean + -- loop through progression states + + disableToggle : boolean + -- disables the toggle part of the item (right click will decrease the progression state instead; if disableProgessive is enabled as well the item will act as a static) + + disableProgessive : boolean + -- disables the progression part of the item (left and right click will toggle instead; if disableToggle is enabled as well the item will act as a static) + + initialStage : integer + -- initial progression state + + initialActive : boolean + -- initial active state + + toggleChildren : list LuaItem + -- also toggles these Items when this item is toggled and enableChildToggle is true (only via :setActive) + + enableChildToggle : boolean + -- enables toggling toggleChildren (only via :setActive) + + progressionChildren : table + { + children : list LuaItem with a continuous, numeric index + states : table + { + [parent_state] = { + [child_index] = child_state + ... + } + ... + } + } + -- set the progression of the progressionChildren.children to the state defined in progressionChildren.states + when this item is progressed to a state equal to a index in progressionChildren.state and enableChildToggle is true (only via :setState) + + enableChildProgression + -- enables progression of progressionChildren (only via :setState) +]] + +function ProgressiveTogglePlus:init(name, code, states, loop, disableToggle, disableProgessive, initialStage, initialActive, toggleChildren, enableChildToggle, + progressionChildren, enableChildProgression) + + self:createItem(name) + self.code = code + self.active = false + self.states = states + self.state = 1 + self.loop = loop + self.disableToggle = disableToggle + self.disableProgessive = disableProgessive + self.toggleChildren = toggleChildren + self.enableChildToggle = enableChildToggle + self.progressionChildren = progressionChildren + self.enableChildProgression = enableChildProgression + + self:getImages() + self:setState(initialStage) + self:setActive(initialActive) + self.ItemInstance.PotentialIcon = self.images[initialStage] +end + +function ProgressiveTogglePlus:getImages() + self.images = {} + for i = 0, #self.states do + self.images[i] = ImageReference:FromPackRelativePath(self.states[i].image) + end +end + +function ProgressiveTogglePlus:getState() + return self.state +end + +function ProgressiveTogglePlus:setState(state) + self:propertyChanged("state",state) + if self.enableChildProgression then + if self.progressionChildren.states[state] then + for k,v in pairs(self.progressionChildren.states[state]) do + if self.progressionChildren.children[k] then + self.progressionChildren.children[k]:setState(v) + end + end + end + end +end + +function ProgressiveTogglePlus:advanceState() + local start = self.state + local target = start + repeat + if target == #self.states then + if self.loop then + target = 0 + end + else + target = self.state + 1 + end + until start == target or not self.states[target].disabled + if target then + self:setState(target) + end +end + +function ProgressiveTogglePlus:decreaseState() + local start = self.state + local target = start + repeat + if target == 0 then + if self.loop then + target = #self.states + end + else + target = self.state - 1 + end + until start == target or not self.states[target].disabled + if target then + self:setState(target) + end +end + +function ProgressiveTogglePlus:setActive(active) + self:setProperty("active",active) + if self.enableChildToggle then + for _,v in pairs(self.toggleChildren) do + v:setActive(active) + end + end +end + +function ProgressiveTogglePlus:getActive() + return self.active +end + +function ProgressiveTogglePlus:updateIcon() + self.ItemInstance.Icon = self.images[self.state] + if PopVersion and PopVersion > "0.1.0" then + if self.active then + self.ItemInstance.IconMods = "" + else + self.ItemInstance.IconMods = "@disabled" + end + end +end + +function ProgressiveTogglePlus:onLeftClick() + if self.disableToggle and self.disableProgessive then return end + if self.disableProgessive then + self:setActive(not self.active) + else + self:advanceState() + end +end + +function ProgressiveTogglePlus:onRightClick() + if self.disableToggle and self.disableProgessive then return end + if self.disableToggle then + self:decreaseState() + else + self:setActive(not self.active) + end +end + +function ProgressiveTogglePlus:canProvideCode(code) + if code == self.code then + return true + else + for i = 0, #self.states do + for code2 in string.gmatch(self.states[i].codes, "[^,]+") do + if code == code2 then + return true + end + end + end + return false + end +end + +function ProgressiveTogglePlus:providesCode(code) + if code == self.code then + return self.state + end + for i = 0, #self.states do + for code2 in string.gmatch(self.states[i].codes, "[^,]+") do + if code == code2 then + return self.state + end + end + end + return 0 +end + +function ProgressiveTogglePlus:advanceToCode(code) +end + +function ProgressiveTogglePlus:save() + local saveData = {} + saveData["state"] = self:getState() + saveData["active"] = self.active + saveData["disableToggle"] = self.disableToggle + saveData["disableProgessive"] = self.disableProgessive + return saveData +end + +function ProgressiveTogglePlus:load(data) + if data["state"] ~= nil then + self:setProperty("state",data["state"]) + end + if data["active"] ~= nil then + self:setProperty("active",data["active"]) + end + if data["disableToggle"] ~= nil then + self:setProperty("disableToggle",data["disableToggle"]) + end + if data["disableProgessive"] ~= nil then + self:setProperty("disableProgessive",data["disableProgessive"]) + end + return true +end + +function ProgressiveTogglePlus:propertyChanged(key, value) + print(string.format("ProgressiveTogglePlus:propertyChanged key %s with value %s",key,value)) + if key == "state" then + self.state = value + end + if key == "active" then + self.active = value + end + if key == "disableToggle" then + self.disableToggle = value + end + if key == "disableProgessive" then + self.disableProgessive = value + end + if key == "enableChildToggle" then + self.enableChildToggle = value + end + if key == "enableChildProgression" then + self.enableChildProgression = value + end + if key == "state" or key == "active" then + self:updateIcon() + end +end diff --git a/scripts/custom_items/progressiveTogglePlusWrapper.lua b/scripts/custom_items/progressiveTogglePlusWrapper.lua new file mode 100644 index 0000000..524d6fe --- /dev/null +++ b/scripts/custom_items/progressiveTogglePlusWrapper.lua @@ -0,0 +1,102 @@ +CustomItemProgressiveTogglePlus = class() + +function CustomItemProgressiveTogglePlus:init() +end + +function CustomItemProgressiveTogglePlus:createItem(name) + local function invokeLeftClick(item) + item.ItemState:onLeftClick() + end + local function invokeRightClick(item) + item.ItemState:onRightClick() + end + local function invokeCanProvideCode(item, code) + return item.ItemState:canProvideCode(code) + end + local function invokeProvidesCode(item, code) + return item.ItemState:providesCode(code) + end + local function invokeAdvanceToCode(item, code) + return item.ItemState:advanceToCode(code) + end + local function invokeSave(item) + return item.ItemState:save() + end + local function invokeLoad(item, data) + return item.ItemState:load(data) + end + local function invokePropertyChanged(item, key, value) + return item.ItemState:propertyChanged(key, value) + end + + self.ItemInstance = ScriptHost:CreateLuaItem() + self.ItemInstance.Name = name + self.ItemInstance.ItemState = self + self.ItemInstance.OnLeftClickFunc = invokeLeftClick + self.ItemInstance.OnRightClickFunc = invokeRightClick + self.ItemInstance.CanProvideCodeFunc = invokeCanProvideCode + self.ItemInstance.ProvidesCodeFunc = invokeProvidesCode + self.ItemInstance.AdvanceToCodeFunc = invokeAdvanceToCode + self.ItemInstance.SaveFunc = invokeSave + self.ItemInstance.LoadFunc = invokeLoad + self.ItemInstance.PropertyChangedFunc = invokePropertyChanged +end + +-- Called when your item is left-clicked +function CustomItemProgressiveTogglePlus:onLeftClick() +end + +-- Called when your item is right-clicked +function CustomItemProgressiveTogglePlus:onRightClick() +end + +-- Called to determine if your item can ever provide a given code +-- This is used (for example) when placing items on item grids. +-- +-- Returns true or false +function CustomItemProgressiveTogglePlus:canProvideCode(code) + return false +end + +-- Called to determine if your item currently provides a given code, +-- and if so, the count provided. +-- +-- Returns an integer count >= 0 +function CustomItemProgressiveTogglePlus:providesCode(code) + return 0 +end + +-- Called to request that your item advance to the given code. +function CustomItemProgressiveTogglePlus:advanceToCode(code) +end + +-- Called when the user is saving progress. +-- +-- Return a table of key-value pairs, for simple value types (bool, integer, string, etc.) +function CustomItemProgressiveTogglePlus:save() + return { } +end + +-- Called when the user is loading progress. Data is a table containing your saved data. +-- +-- Return true for success, false for failure (will fail the load) +function CustomItemProgressiveTogglePlus:load(data) + return true +end + +-- Call to set a transaction-backed property. Properties set this way will support undo. +-- +-- Returns true if the value was actually modified. DO NOT OVERRIDE +function CustomItemProgressiveTogglePlus:setProperty(key, value) + return self.ItemInstance:Set(key, value) +end + +-- Call to read a transaction-backed property. DO NOT OVERRIDE +function CustomItemProgressiveTogglePlus:getProperty(key) + return self.ItemInstance:Get(key) +end + +-- Called when a transaction-backed property's value has changed. This will also happen +-- as part of setting a transaction-backed property. +function CustomItemProgressiveTogglePlus:propertyChanged(key, value) +end \ No newline at end of file diff --git a/scripts/init.lua b/scripts/init.lua new file mode 100644 index 0000000..9369d82 --- /dev/null +++ b/scripts/init.lua @@ -0,0 +1,44 @@ +-- entry point for all lua code of the pack +-- more info on the lua API: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#lua-interface +ENABLE_DEBUG_LOG = true +-- get current variant +local variant = Tracker.ActiveVariantUID +-- check variant info +IS_ITEMS_ONLY = variant:find("itemsonly") + +print("-- Example Tracker --") +print("Loaded variant: ", variant) +if ENABLE_DEBUG_LOG then + print("Debug logging is enabled!") +end + +-- Utility Script for helper functions etc. +ScriptHost:LoadScript("scripts/utils.lua") + +-- Logic +ScriptHost:LoadScript("scripts/logic/logic.lua") + +-- Custom Items +ScriptHost:LoadScript("scripts/custom_items/class.lua") +ScriptHost:LoadScript("scripts/custom_items/progressiveTogglePlus.lua") +ScriptHost:LoadScript("scripts/custom_items/progressiveTogglePlusWrapper.lua") + +-- Items +Tracker:AddItems("items/items.json") + +if not IS_ITEMS_ONLY then -- <--- use variant info to optimize loading + -- Maps + Tracker:AddMaps("maps/maps.json") + -- Locations + Tracker:AddLocations("locations/locations.json") +end + +-- Layout +Tracker:AddLayouts("layouts/items.json") +Tracker:AddLayouts("layouts/tracker.json") +Tracker:AddLayouts("layouts/broadcast.json") + +-- AutoTracking for Poptracker +if PopVersion and PopVersion >= "0.18.0" then + ScriptHost:LoadScript("scripts/autotracking.lua") +end diff --git a/scripts/logic/logic.lua b/scripts/logic/logic.lua new file mode 100644 index 0000000..3100d0d --- /dev/null +++ b/scripts/logic/logic.lua @@ -0,0 +1,15 @@ +-- put logic functions here using the Lua API: https://github.com/black-sliver/PopTracker/blob/master/doc/PACKS.md#lua-interface +-- don't be afraid to use custom logic functions. it will make many things a lot easier to maintain, for example by adding logging. +-- to see how this function gets called, check: locations/locations.json +-- example: +function has_more_then_n_consumable(n) + local count = Tracker:ProviderCountForCode('consumable') + local val = (count > tonumber(n)) + if ENABLE_DEBUG_LOG then + print(string.format("called has_more_then_n_consumable: count: %s, n: %s, val: %s", count, n, val)) + end + if val then + return 1 -- 1 => access is in logic + end + return 0 -- 0 => no access +end \ No newline at end of file diff --git a/scripts/utils.lua b/scripts/utils.lua new file mode 100644 index 0000000..1fd1e09 --- /dev/null +++ b/scripts/utils.lua @@ -0,0 +1,21 @@ +-- from https://stackoverflow.com/questions/9168058/how-to-dump-a-table-to-console +-- dumps a table in a readable string +function dump_table(o, depth) + if depth == nil then + depth = 0 + end + if type(o) == 'table' then + local tabs = ('\t'):rep(depth) + local tabs2 = ('\t'):rep(depth + 1) + local s = '{\n' + for k, v in pairs(o) do + if type(k) ~= 'number' then + k = '"' .. k .. '"' + end + s = s .. tabs2 .. '[' .. k .. '] = ' .. dump_table(v, depth + 1) .. ',\n' + end + return s .. tabs .. '}' + else + return tostring(o) + end +end diff --git a/var_itemsonly/layouts/tracker.json b/var_itemsonly/layouts/tracker.json new file mode 100644 index 0000000..07319cb --- /dev/null +++ b/var_itemsonly/layouts/tracker.json @@ -0,0 +1,80 @@ +{ + "tracker_default": { + "type": "container", + "background": "#000000", + "content": { + "type": "dock", + "dropshadow": true, + "content": [ + { + "type": "dock", + "dock": "left", + "v_alignment": "stretch", + "content": [ + { + "type": "group", + "header": "Items", + "dock": "top", + "content": { + "type": "layout", + "key": "shared_item_grid" + } + } + ] + } + ] + } + }, + "tracker_horizontal": { + "type": "container", + "background": "#000000", + "content": { + "type": "dock", + "dropshadow": true, + "content": [ + { + "type": "dock", + "dock": "bottom", + "content": [ + { + "type": "group", + "header": "Items", + "dock": "left", + "margin": "0,0,3,0", + "content": { + "type": "layout", + "h_alignment": "center", + "v_alignment": "center", + "key": "shared_item_grid" + } + }, + /*{ + "type": "group", + "header": "Doors", + "dock": "left", + "margin": "0,0,3,0", + "content": { + "type": "layout", + "h_alignment": "center", + "v_alignment": "center", + "key": "doors_item_grid" + } + },*/ + { + "type": "group", + "header": "Transitions", + "dock": "left", + "margin": "0,0,3,0", + "content": { + "type": "layout", + "h_alignment": "center", + "v_alignment": "center", + "key": "transitions_item_grid" + } + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/var_itemsonly/variants.md b/var_itemsonly/variants.md new file mode 100644 index 0000000..2a06a2b --- /dev/null +++ b/var_itemsonly/variants.md @@ -0,0 +1,8 @@ +# Variants + +Variants can override many files, for example: items, maps, locations, layouts and scripts. + +You override file just by put them in the same place and with the name inside the variants folder. +Example: you want to overide ``layouts/tracker.json`` in the ``var_itemsonly`` variant -> place the file at ``var_itemsonly/locations/tracker.json`` + +In most cases overriding layouts is all you need so thats what I've done in this example. \ No newline at end of file