Mach131

Exceed Custom Character Board

Jan 26th, 2026 (edited)
113
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 11.93 KB | None | 0 0
  1. board_image_factor = 11/12 --estimate of how much of board model is the actual image
  2. image_dimensions = Vector(1200, 0, 900)
  3. button_width = 650 --not sure what this means size-wise, might need to play around
  4. debug_button_visible = 0 --set to 1 to see the button positioning
  5.  
  6. -- shouldn't need to change this
  7. board_size = self.getBounds().size * board_image_factor
  8.  
  9. -- can define variables for character labels here to minimize chance of typos
  10. -- these only work if the value matches the variable name; will think about why later
  11. char_frost = 'char_frost'
  12. char_redhood = 'char_redhood'
  13. char_sunny = 'char_sunny'
  14. char_arc = 'char_arc'
  15. char_depths = 'char_depths'
  16. char_wyrm = 'char_wyrm'
  17. char_lich = 'char_lich'
  18. char_storm = 'char_storm'
  19. char_season_mns = 'char_season_mns'
  20.  
  21. char_marisa = 'char_marisa'
  22. char_suwako = 'char_suwako'
  23. char_ran = 'char_ran'
  24. char_suika = 'char_suika'
  25. char_kokoro = 'char_kokoro'
  26. char_junko = 'char_junko'
  27. char_seija = 'char_seija'
  28. char_season_2hu = 'char_season_2hu'
  29.  
  30. char_season_all = 'char_season_all'
  31. char_season_focus = 'char_season_focus'
  32.  
  33. -- indicate where your character's square is on your board, in pixels
  34. character_coordinate_map = {
  35.     char_frost = {150, 100},
  36.     char_redhood = {250, 100},
  37.     char_sunny = {350, 100},
  38.     char_arc = {150, 200},
  39.     char_depths = {350, 200},
  40.     char_wyrm = {150, 300},
  41.     char_lich = {250, 300},
  42.     char_storm = {350, 300},
  43.     char_season_mns = {250, 200},
  44.  
  45.     char_marisa = {500, 100},
  46.     char_suwako = {600, 100},
  47.     char_ran = {700, 100},
  48.     char_suika = {500, 200},
  49.     char_kokoro = {700, 200},
  50.     char_junko = {500, 300},
  51.     char_seija = {700, 300},
  52.     char_season_2hu = {600, 200},
  53.  
  54.     char_season_all = {175, 825},
  55.     char_season_focus = {1025, 825}
  56. }
  57.  
  58. -- can include entries in this for differently sized buttons (as multipliers to width/height)
  59. button_resize_map = {
  60.     -- char_season_all = {1.5, 1.5},
  61.     -- char_season_focus = {1.5, 1.5}
  62. }
  63.  
  64. -- tooltips for each button, likely character names
  65. character_tooltip_map = {
  66.     char_frost = 'Hero of Frost (Maiden & Spell)',
  67.     char_redhood = 'Silent Redhood (Maiden & Spell)',
  68.     char_sunny = 'Sun Priestess (Maiden & Spell)',
  69.     char_arc = 'Royal Arcanist (Maiden & Spell)',
  70.     char_depths = 'Depth\'s Secret (Maiden & Spell)',
  71.     char_wyrm = 'Dreadwyrm Heir (Maiden & Spell)',
  72.     char_lich = 'Lich of Flowers (Maiden & Spell)',
  73.     char_storm = 'Stormbeast (Maiden & Spell)',
  74.     char_season_mns = 'Maiden & Spell',
  75.  
  76.     char_marisa = 'Kirisame Marisa (Touhou Project)',
  77.     char_suwako = 'Moriya Suwako (Touhou Project)',
  78.     char_ran = 'Yakumo Ran (Touhou Project)',
  79.     char_suika = 'Ibuki Suika (Touhou Project)',
  80.     char_kokoro = 'Hata no Kokoro (Touhou Project)',
  81.     char_junko = 'Junko (Touhou Project)',
  82.     char_seija = 'Kijin Seija (Touhou Project)',
  83.     char_season_2hu = 'Touhou Project',
  84.  
  85.     char_season_all = 'All Characters',
  86.     char_season_focus = 'Test These Guys'
  87. }
  88.  
  89.  
  90. -- for random buttons, indicate which characters they can roll
  91. character_collection_map = {
  92.     char_season_mns = {
  93.         characters = {char_frost, char_redhood, char_sunny, char_arc,
  94.             char_depths, char_wyrm, char_lich, char_storm},
  95.         color_string = [[{
  96.             "r": 0,
  97.             "g": 0.89,
  98.             "b": 0.13
  99.         }]]
  100.     },                  
  101.     char_season_2hu = {
  102.         characters = {char_marisa, char_suwako, char_ran, char_suika,
  103.             char_kokoro, char_junko, char_seija},
  104.         color_string = [[{
  105.             "r": 1,
  106.             "g": 0.2,
  107.             "b": 0.3
  108.         }]]
  109.     },
  110.  
  111.     char_season_all = {
  112.         characters = {char_frost, char_redhood, char_sunny, char_arc, char_depths, char_wyrm, char_lich,
  113.             char_storm, char_marisa, char_suwako, char_ran, char_suika, char_kokoro, char_junko, char_seija},
  114.         color_string = [[{
  115.             "r": 0,
  116.             "g": 0.6,
  117.             "b": 0
  118.         }]]
  119.     },
  120.     -- special 'season' for characters i want people to test
  121.     char_season_focus = {
  122.         characters = {char_redhood, char_suika, char_suwako, char_kokoro},
  123.         color_string = [[{
  124.             "r": 0,
  125.             "g": 0.46,
  126.             "b": 0.89
  127.         }]]
  128.     }
  129. }
  130.  
  131. -- for everyone else, map them to their jsons; this will prob be pretty long
  132. -- note that if copying from a saved object, you only want the section under the 'ObjectStates' field, not the full json
  133. -- i 'dehydrated' the jsons by taking out newlines for readability, depend on text editor tho
  134. char_json_map = {
  135.     -- char_frost = [[{  "GUID": <--other stuff removed for brevity-->}]],
  136.     -- char_redhood = [[{  "GUID": <--other stuff removed for brevity-->}]],
  137.     -- char_sunny = [[{  "GUID": <--other stuff removed for brevity-->}]],
  138.     -- char_arc = [[{  "GUID": <--other stuff removed for brevity-->}]],
  139.     -- char_depths = [[{  "GUID": <--other stuff removed for brevity-->}]],
  140.     -- char_wyrm = [[{  "GUID": <--other stuff removed for brevity-->}]],
  141.     -- char_lich = [[{  "GUID": <--other stuff removed for brevity-->}]],
  142.     -- char_storm = [[{  "GUID": <--other stuff removed for brevity-->}]],
  143.  
  144.     -- char_marisa = [[{  "GUID": <--other stuff removed for brevity-->}]],
  145.     -- char_suwako = [[{  "GUID": <--other stuff removed for brevity-->}]],
  146.     -- char_ran = [[{  "GUID": <--other stuff removed for brevity-->}]],
  147.     -- char_suika = [[{  "GUID": <--other stuff removed for brevity-->}]],
  148.     -- char_kokoro = [[{  "GUID": <--other stuff removed for brevity-->}]],
  149.     -- char_junko = [[{  "GUID": <--other stuff removed for brevity-->}]],
  150.     -- char_seija = [[{  "GUID": <--other stuff removed for brevity-->}]]
  151. }
  152.  
  153. -- end of big json variable
  154.  
  155. board_dim_ratio = image_dimensions.x / image_dimensions.z
  156. did_initialize = false
  157.  
  158. function onload()
  159.     if did_initialize then
  160.         return
  161.     end
  162.  
  163.     did_initialize = true
  164.     local base_rotation = self.getRotation()
  165.     self.setRotation(Vector(0, 180, 0))
  166.     board_size = self.getBounds().size * board_image_factor
  167.  
  168.     for char_label, button_pos_px in pairs(character_coordinate_map) do
  169.         local button_pos = pixelToRelativeWorldPosition(button_pos_px[1], button_pos_px[2], true)
  170.         local char_tooltip = character_tooltip_map[char_label]
  171.         if character_collection_map[char_label] ~= nil then
  172.             char_tooltip = char_tooltip .. "\n(Left Click for Bag, Right Click for Random)"
  173.         end
  174.         local width_mult = 1
  175.         local height_mult = 1
  176.         if button_resize_map[char_label] ~= nil then
  177.             width_mult = button_resize_map[char_label][1]
  178.             height_mult = button_resize_map[char_label][2]
  179.         end
  180.         self.createButton({
  181.             click_function = attachArg('spawnCharacter', char_label),
  182.             function_owner = self,
  183.             position = button_pos + Vector(0, -0.2 * (0.5 - debug_button_visible), 0),
  184.             width = button_width * width_mult,
  185.             height = button_width * board_dim_ratio * height_mult,
  186.             tooltip = char_tooltip
  187.         })
  188.     end
  189.  
  190.     self.setRotation(base_rotation)
  191. end
  192.  
  193. function attachArg(click_function, arg)
  194.     local fn_name = click_function .. arg
  195.     _G[fn_name] = function(obj, player_color, alt_click)
  196.         _G[click_function](obj, player_color, alt_click, arg)
  197.     end
  198.     return fn_name
  199. end
  200.  
  201. function spawnCharacter(_, player_color, alt_click, char_label)
  202.     --print('yippee ' .. char_label)
  203.    
  204.     -- The clicks are caught by UI elements, which Grey actually CAN interact with.
  205.     -- Nobody actually expects this, so we should ignore Grey's clicks.
  206.     if player_color == "Grey" then return end
  207.  
  208.     local spawned_object = nil
  209.     local is_season_bag = false
  210.  
  211.     -- Check if it's a collection; if so, choose a random appropriate char_label
  212.     local check_collection = character_collection_map[char_label]
  213.     if check_collection ~= nil then
  214.         collection_characters = check_collection.characters
  215.         collection_colors = check_collection.color_string
  216.  
  217.         if alt_click then -- if right click, spawn a random character
  218.             char_label = collection_characters[math.random(#collection_characters)]
  219.         else -- if left click, spawn the full collection
  220.             local season_json_list = {}
  221.             for _, single_json in ipairs(collection_characters) do
  222.                 table.insert(season_json_list, char_json_map[single_json])
  223.             end
  224.             local season_json_list_string = table.concat(season_json_list, ", ")
  225.  
  226.             spawned_object = spawnObjectJSON({
  227.                 json = [[{
  228.                     "Name": "Bag",
  229.                     "Transform": {
  230.                         "posX": 0,
  231.                         "posY": 0,
  232.                         "posZ": 0,
  233.                         "rotX": 0,
  234.                         "rotY": 0,
  235.                         "rotZ": 0,
  236.                         "scaleX": 1,
  237.                         "scaleY": 1,
  238.                         "scaleZ": 1
  239.                     },
  240.                     "ColorDiffuse": ]] .. collection_colors .. [[,
  241.                     "Nickname": "All Characters - ]] .. character_tooltip_map[char_label].. [[",
  242.                     "ContainedObjects": [
  243.                     ]] .. season_json_list_string .. [[
  244.                     ]
  245.                 }]]
  246.             })
  247.             spawned_object.use_hands = true
  248.             is_season_bag = true
  249.         end
  250.     end
  251.  
  252.     -- Create bag
  253.     if spawned_object == nil then
  254.         spawned_object = spawnObjectJSON({
  255.             json = char_json_map[char_label]
  256.         })
  257.         spawned_object.use_hands = true
  258.         spawned_object.memo = "selfdestruct"
  259.     end
  260.    
  261.     -- stolen code to move bag into player's hand
  262.     local playerReference = Player[player_color]
  263.     local charPosition = Vector(0, 0, 0)
  264.     local screenRotation = self.getRotation()
  265.     local xPositionOffset = 0
  266.     local zPositionOffset = 0
  267.     local yPositionOffset = 0
  268.  
  269.     local facingFactor = 1
  270.     if screenRotation.z >= 90 and screenRotation.z < 270 then
  271.         facingFactor = -1
  272.     end
  273.  
  274.     if screenRotation.y >= 45 and screenRotation.y < 135 then
  275.         xPositionOffset = -1.1*facingFactor*charPosition.z
  276.         yPositionOffset = charPosition.y
  277.         zPositionOffset = -1.1*charPosition.x
  278.     elseif screenRotation.y >= 135 and screenRotation.y < 225 then
  279.         xPositionOffset = -1.1*facingFactor*charPosition.x
  280.         yPositionOffset = facingFactor*charPosition.y
  281.         zPositionOffset = 1.1*charPosition.z
  282.     elseif screenRotation.y >= 225 and screenRotation.y < 315 then
  283.         xPositionOffset = 1.1*facingFactor*charPosition.z
  284.         yPositionOffset = facingFactor*charPosition.y
  285.         zPositionOffset = 1.1*charPosition.x
  286.     elseif screenRotation.y >= 315 or screenRotation.y < 45 then
  287.         xPositionOffset = 1.1*facingFactor*charPosition.x
  288.         yPositionOffset = facingFactor*charPosition.y
  289.         zPositionOffset = -1.1*charPosition.z
  290.     end
  291.     local playerHandTransform = playerReference.getHandTransform() or {
  292.         position = {
  293.             x = xPositionOffset + self.getPosition().x,
  294.             y = 10 + yPositionOffset + self.getPosition().y,
  295.             z = zPositionOffset + self.getPosition().z,
  296.         },
  297.         rotation = self.getRotation(),
  298.     }
  299.  
  300.     spawned_object.setPosition(playerHandTransform.position);
  301.     spawned_object.setRotation(playerHandTransform.rotation);
  302.     if not is_season_bag then
  303.         spawned_object.setScale(Vector(0.9, 0.6, 0.9))
  304.     end
  305. end
  306.  
  307. function pixelToRelativeWorldPosition(x, z, return_local)
  308.     local x_ratio = ((x / image_dimensions.x) - 0.5) * 2
  309.     local z_ratio = ((z / image_dimensions.z) - 0.5) * -2
  310.  
  311.     local result = Vector(
  312.         (board_size.x / 2) * x_ratio,
  313.         board_size.y,
  314.         (board_size.z / 2) * z_ratio
  315.     )
  316.  
  317.     if return_local then
  318.         result.x = result.x * -1
  319.         result = self.positionToLocal(self.getPosition() + result)
  320.     end
  321.  
  322.     return result
  323. end
Advertisement
Add Comment
Please, Sign In to add comment