Twitch streamer Djackzz discovered that my mods (in particular Playable Arcade Machines, Simple Playable Pianos, and Silly Object Sounds) didn't work with the controller. He discovered this while playing co-op on his stream.
Since not many people play Project Zomboid co-op, I didn't consider this a high priority. Plus, before 41.51 I couldn't get my Xbox One controller to work with Project Zomboid (I use Linux as my OS).
Since 41.53 has been out for over a month now I decided it was time to find my Xbox One controller and fix this bug. It took me over an hour to find my controller, because instead of being in the first places I looked (with my cables, with my extra computer parts, in a bin with assorted tech junk), it was on top of my Xbox.
Playable Arcade Machines was my first mod, and one of the most important things my mod does is create a menu item to "Play" the particular game. I don't remember where I looked for examples of that, but the good examples basically looked like this one that I'm taking from the ISBBQMenu:
function ISBBQMenu.OnFillWorldObjectContextMenu(player, context, worldobjects, test) if test and ISWorldObjectContextMenu.Test then return true end local bbq = nil local objects = {} for _,object in ipairs(worldobjects) do local square = object:getSquare() if square then for i=1,square:getObjects():size() do local object2 = square:getObjects():get(i-1) if instanceof(object2, "IsoBarbecue") then bbq = object2 end end end end if not bbq then return end
Not knowing anything about Project Zomboid mods I guessed the following:
- player - Was an object related to the player
- context - had to do something with the "context menu" I was trying to add my commands to
- worldobjects - some kind of key value pair object containing a bunch of objects (probably near the character)
- test - test variable I didn't need to worry about
Briefly, this code cycles through all of the worldobjects to see if any of them are on squares that also contain a BBQ. Once the code figures out which object is the BBQ, it can then add the menu item for the BBQ Info.
But I also saw some mod examples that didn't go through this whole process to find the object it wanted to activate. Instead of looping through a bunch of worldobjects, they just picked the first one:
local thisObject = worldobjects[1];
When I tested out using just this in my Playable Arcade Machines mod, it worked. And from a time-complexity standpoint, it was much better. I didn't know if worldobjects was large or small. If it were large, it seemed like a huge waste of time to search through all of the values in worldobjects when worldobjects[1] had what I wanted! I mean, just the name "worldobjects" implies it could be everything in the world! So I did the same for my piano mod, and for my silly object sounds mod.
But for controller it didn't work, I would later find. A mouse can usually pick the exact object I want to activate, so worldobjects[1] usually works.
But the controller can't know the exact pixel you've selected - you can only stand near the object you want to activate. You might be standing in the right spot, and pointing to the right object. But it's really hard to tell. Through experimentation it looks to me like worldobjects[1] is the ground square in front of the character all the time, and I don't really think worldobjects[1] will ever be the arcade machine (or piano) in front of the character. Also in my experiments, it looks like it only pulls around 5 objects into worldobjects, so it isn't examining too many more squares.
So for my Simple Playable Pianos mod, I changed it to loop through all the worldobjects:
for _,object in ipairs(worldobjects) do local square = object:getSquare() if square then for i=1,square:getObjects():size() do local thisObject = square:getObjects():get(i-1) local properties = thisObject:getSprite():getProperties() if properties ~= nil then local groupName = nil local customName = nil local thisSpriteName = nil local thisSprite = thisObject:getSprite() if thisSprite:getName() then thisSpriteName = thisSprite:getName() --print("PseudoEdSPP: Sprite Name is " .. spriteName) end if properties:Is("GroupName") then groupName = properties:Val("GroupName") --print("PseudoEdSPP: Sprite GroupName: " .. groupName); end if properties:Is("CustomName") then customName = properties:Val("CustomName") --print("PseudoEdSPP: Sprite CustomName: " .. customName); end -- For Pianos, Custom Name = "Piano". -- In vanilla PZ, GroupName = "Western", but leave that open for modders if customName == "Piano" then piano = thisObject; spriteName = thisSpriteName; end end end end end
So now it follows these basic steps:
- for each of the world objects
- select the square the object is on
- check all the objects on that square, looking for a piano
And if if finds the object is a piano, it stores it as piano so I can add the menu items later...
It might be cycling through a few more objects than necessary for mouse users, but this is necessary for controllers. I'll be fixing the rest of my mods in the next few days.
Thanks for reading! If you notice any mistakes, or if you have any questions you'd like me to answer, please let me know!
I'm trying to do some modding so I may not be posting as often as I have been recently. But maybe I will post. I'm trying to figure it out.
No comments:
Post a Comment