How can I efficiently respond to ingame target selection events? - events

I come from a C#/Java background and have never touched Lua before.
I want the addon to show a message (default message window) that prints out the class of the target whenever I click on and target another player, and ONLY when I target a player. I have two files, SpeccySpecs.lua (contains the functions needed to handle the event) and SpeccySpecs.xml (contains the frame to run the function). Whenever I run the addon ingame, I am getting nil errors because my Core.lua file is returning nil when requiring the .xml file and my .xml file returns a nil from the OnTarget() function in SpeccySpecs.lua.
I have tried to solve this in multiple ways, one of which was by creating a local table and requiring it in Core.lua, where I'd eventually call the function inside the table, but it also returned nil. I've been using
sites such as:
https://wowpedia.fandom.com/wiki/Events
https://wowwiki-archive.fandom.com/wiki/Event_API
https://www.lua.org/
But what I've tried simply hasn't worked, and I assume I'm forgetting something small in the Lua code.
SpeccySpecs.lua
local function getPlayerClass()
local playerClassName = nil;
local playerClassFilename = nil;
local playerClassId = nil;
if UnitClass("target") ~= nil
then
playerClassName, playerClassFilename, playerClassId = UnitClass("target");
if playerClassName == 1 -- Warrior
then message ("It's a warrior")
elseif playerClassId == 2 -- Paladin
then message ("It's a paladin")
elseif playerClassId == 3 -- Hunter
then message ("It's a hunter")
elseif playerClassId == 4 -- Rogue
then message ("It's a rogue")
elseif playerClassId == 5 -- Priest
then message ("It's a priest")
elseif playerClassId == 6 -- Death Knight
then message ("It's a death knight")
elseif playerClassId == 7 -- Shaman
then message ("It's a shaman")
elseif playerClassId == 8 -- Mage
then message ("It's a mage")
elseif playerClassId == 9 -- Warlock
then message ("It's a warlock")
elseif playerClassId == 10 -- Monk
then message ("It's a monk")
elseif playerClassId == 11 -- Druid
then message ("It's a druid")
elseif playerClassId == 12 -- Demon Hunter
then message ("It's a demon hunter")
else message ("That class does not exist")
end
else message ("Please target a player")
end
end
function OnTarget(self, event, ...)
print(event)
return getPlayerClass()
end
SpeccySpecs.xml
<UI>
<Script file="SpeccySpecs.lua"/>
<Frame name="SpeccyFrame">
<Scripts>
<OnEvent function="OnTarget"/>
</Scripts>
</Frame>
</UI>
Core.lua
PlayerClassFrame = require "SpeccySpecs.xml";
return PlayerClassFrame
Thanks in advance for the help, everyone.

Goes like this:
make frame (yours is in xml) > OnLoad event handler > register for events > event fired > handle events
1) You don't need the require or the core.lua:
WoW has its own explicit ordered loading process that uses toc files and includes in xml.
Your toc file probably looks like:
# some stuff
MyAddon.xml
Then your MyAddon.xml loads:
<UI>
<Script file="MyAddon.lua"/>
<Frame name="MyAddonFrame">
Which then loads the .lua file first, so that all the things in the .lua file are available to be referenced as the frame xml loads.
2) You are missing registering for a specific event:
For example:
<OnLoad> self:RegisterEvent("PLAYER_ENTERING_WORLD") </OnLoad>
So that you end up with something like:
<OnLoad> self:RegisterEvent("PLAYER_ENTERING_WORLD") </OnLoad>
<OnEvent> print("Event name " .. event) </OnEvent>
You need to go find a specific event that deals with when something is targeted that comes as close as possible to fitting what you need.
3) You need to pass the telemetry from the event to the handler function:
<OnEvent> MyAddon:OnEvent(self, event, ...) </OnEvent>
The self is the current frame, event is given to you hind the scenes as OnEvent here just becomes compiled into a regular Lua function and gets event passed to it, and ... is a Lua type of list used for unnamed parameters, which you can break out into Lua variables later.
4) You need a repeatable way to expose your Lua code across files
This needs to be in a way that does not clash with WoW or other addons.
MyAddon = {}
Is one way. This makes a single Lua table with a global unique name that you can stick functions in so they can be found elsewhere outside that one file.
Anything in WoW that is not 'local' goes into a single environment Lua global name space across all addons and wow.
5) You need to accept the parameters for telemetry from the event:
And process the telemetry to eek out what you more specifically need
function MyAddon:OnEvent(frame, event, ...)
if event == "SOME_EVENT_NAME" then
local _, name = ...
if is this some player then
MyAddon:SomePlayerDidSomething(name)
end
elseif event == "SOME_EVENT_NAME" then
end
end
This should get you started for the basic addon process.
After all that is working, then you need a way to make the business end more data driven, which will in theory make it more code-size and speed efficient.
6) Add a table to lookup strings for ids:
MyAddon.classNames = {
[1] = "warrior",
[2] = "paladin",
[3] = "hunter",
}
See https://www.lua.org/pil/3.6.html
Which will allow you to construct code more like this:
local className = self.classNames[playerClassId]
if className then
message("It's a " .. className .. ".")
end
This should get you started overall. It's hard to find the right resource often to get you off the ground at the very beginning. You got pretty far.

You don't necessarily need an XML file, it can all be done in Lua. Also, require() does not exist in the WoW environment, that's handled by listing your files in the TOC
The page you linked has the PLAYER_TARGET_CHANGED event. You can check if a unit is player with UnitIsPlayer()
Your code would look something like this:
SpeccySpecs.toc
## Interface: 90105
## Version: 1.0.0
## Title: SpeccySpecs
## Notes: Some Description
## Author: YourName
SpeccySpecs.lua
SpeccySpecs.lua
local classes = {
[1] = "warrior",
[2] = "paladin",
[3] = "hunter",
[4] = "rogue",
[5] = "priest",
[6] = "death knight",
[7] = "shaman",
[8] = "mage",
[9] = "warlock",
[10] = "monk",
[11] = "druid",
[12] = "demon hunter",
}
local function getPlayerClass(unit)
if UnitExists(unit) then
local text
if UnitIsPlayer(unit) then
local _, _, classId = UnitClass(unit)
local name = classes[classId]
if name then
text = "It's a "..name
else
text = "That class does not exist"
end
else
text = "Please target a player"
end
message(text)
end
end
local function OnEvent(self, event)
getPlayerClass("target")
end
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_TARGET_CHANGED")
f:SetScript("OnEvent", OnEvent)
Or as a minimal example:
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_TARGET_CHANGED")
f:SetScript("OnEvent", function(self, event)
if UnitIsPlayer("target") then
print("It's a "..UnitClass("target"))
end
end)

Related

Roblox Studio: Why does for loop not working on Roblox Studio?

So I am making a CameraScript on roblox studio that when the player touches a robot, the camera focuses on the robot. But the for loop seems to not work though.
The script in game.StarterPlayer.StarterPlayerScripts:
workspace.CurrentCamera.CameraType = Enum.CameraType.Scriptable
game.Players.LocalPlayer.CharacterAdded:Connect(function(char)
local g = char.Name
print(g) --Just for debugging purposes
print("Player Loaded!")
tou(char)
end)
function tou(char)
print("Function had ran")
for _,p in pairs(char:GetChildren()) do
print("We're here loopin ur parts...")
p.Touched:Connect(function(hit)
print("Someone touched?")
if hit.Parent.Name == "Robot" and hit.Parent:IsA("Model") then
print("It's the robot!")
workspace.CurrentCamera.CFrame = hit.Parent.Look.CFrame
workspace.CurrentCamera.Focus = hit.Parent.Head.CFrame
print("Camlock should be successfull...")
else
print("That ain't a robot tho...")
end
end)
end
end
This is the piece of code that doesn't work:
for _,p in pairs(char:GetChildren()) do
print("We're here loopin ur parts...")
p.Touched:Connect(function(hit)
print("Someone touched?")
if hit.Parent.Name == "Robot" and hit.Parent:IsA("Model") then
print("It's the robot!")
workspace.CurrentCamera.CFrame = hit.Parent.Look.CFrame
workspace.CurrentCamera.Focus = hit.Parent.Head.CFrame
print("Camlock should be successfull...")
else
print("That ain't a robot tho...")
end
end)
end
I tried putting the for loop inside directly the CharacterAdded event, putting print() for debugging but it only printed these:
17:55:24.242 <username> - Client - CamLockOnKill:5
17:55:24.243 Player Loaded! - Client - CamLockOnKill:6
17:55:24.243 Function had ran - Client - CamLockOnKill:12
...but it didn't print the others.
It doesn't print We're here loopin ur parts... so the loop isn't run.
The only way to not run a generic for loop like
for _,p in pairs(char:GetChildren()) do
end
without errors is to provide an empty table to pairs.
So char does not have any children. Find out why you think it has children and why it does not.

Ruby is there a way to stop the user from calling a function/procedure through case before they have accessed a different function/procedure?

I have a text file that I want to open first for reading or writing, but want the user to manually enter the text_file name (which opens the file for reading) first like so:
def read_in_albums
puts "Enter file name: "
begin
file_name = gets().chomp
if (file_name == "albums.txt")
puts "File is open"
a_file = File.new("#{file_name}", "r")
puts a_file.gets
finished = true
else
puts "Please re-enter file name: "
end
end until finished
end
From this unfinished code below, selecting 1 would go to the above procedure. I want the user to select 1 first, and if they choose 2 without having gone through read_in_albums they just get some sort of message like "no file selected and sent back to menu screen.
def main()
finished = false
begin
puts("Main Menu:")
puts("1- Read in Album")
puts("2- Display Album Info")
puts("3- Play Album")
puts("4- Update Album")
puts("5- Exit")
choice = read_integer_in_range("Please enter your choice:", 1, 5)
case choice
when 1
read_in_albums
when 2
display_album_info
when 5
finished = true
end
end until finished
end
main()
The only thing I can think of is something like
when 2
if(read_in_albums == true)
display_album_info
and have it return true from read_in_albums.
which I don't want to do as it just goes through read_in_albums again, when I only want it to do that if the user pressed 1.
All of your application's functionality depends on whether the album data has been read. You are no doubt storing this data as an object in memory referenced by some variable.
$album_data = File.read 'album.txt'
You can test whether this data is present in order to determine whether the file data has been read:
if $album_data.nil?
# ask user for album file
else
# show album user interface
end
There is no need for a separate flag. The mere presence of the data in memory serves as a flag already.
You could either set a flag when option 1 was selcted
has_been_read = false
...
when 1
read_in_albums
has_been_read = true
when 2
if has_been_read
display_album_info
else
puts "Select Option 1 first"
end
Or just test if your file name is a valid string.

How to create a Roblox game where the player has to guess a randomly generated pin?

So, I've been working on this for the past week. I have tried everything (based on the knowledge I know) and yet nothing... my code didn't work the first time, the second time, the third time... the forth... etc... at the end, I let frustration take control of me and I ended up deleting the whole script. Luckily not the parts and models, otherwise I'm really screwed...
I need to create a game in which I have to create a keypad of sorts, at first I thought GUI would work... no, it needs to be SurfaceGUI, which I don't know how to handle well... Anyway, I needed to create a keypad using SurfaceGUI, and display it on a separate screen, as a typical keypad would...
The Player would first have to enter an "initial" number, meaning in order to enter the randomly generated number he first needed to input the static pin in order to "log in," after that, then he would try to guess the number...
I've literally tried everything I could but nothing... It's mainly because of my lack of experience in LUA, I'm more advanced in Python and barely know a thing in Java... If someone could assist me on how to do this, I would appreciate it greatly
First, download this and put it in a ScreenGui in StarterGui. Then, use the following LocalScript placed inside the PIN frame:
-- Script settings
local len = 4 -- replace this as needed...
local regen = false -- determines whether PIN will regenerate after a failed attempt
local regmes = "Enter PIN..." -- start message of PIN pad
local badmes = "Wrong PIN!" -- message displayed when PIN is wrong
local success = "Correct PIN!" -- message displayed when PIN is right
-- Script workings
local pin = script.Parent
local top = pin.Top
local txt = top.Numbers
local nums = top.NumKeys
local pin
local stpin
local nms
txt.Text = regmes
local see = game:GetStorage("ReplicatedStorage").PINActivate
local function activate()
if pin.Visible then
pin.Visible = false
for _, btn in pairs(nums:GetChildren()) do
btn.Active = false
end
return
else
pin.Visible = true
for _, btn in pairs(nums:GetChildren()) do
btn.Active = true
end
return
end
end
local function rand()
math.randomseed(os.time) -- better random numbers this way
return tostring(math.floor(math.random(0,9.9)))
end
local function gen()
nms = {rand()}
for i=2, len, 1 do
nms[#nms+1]=rand()
end
stpin = nms[1]
for i=2, #nms, 1 do
stpin = stpin..nms[i]
end
pin = tonumber(stpin) -- converts the number string into an actual number
end
gen()
local function activate(str)
if tonumber(str) ~= pin then
txt.Text = badmes
wait(2)
txt.Text = regmes
if regen then
gen()
wait(0.1)
end
return
else
txt.Text = success
wait(2)
activate()
-- insert code here...
end
end
for _, btn in pairs(nums:GetChildren()) do
btn.Activated:Connect(function()
if txt.Text == "Wrong PIN!" then return end
txt.Text = txt.Text..btn.Text
if string.len(txt.Text) >= len then
activate(txt.Text)
end
wait(0.1)
end)
end
see.OnClientEvent:Connect(activate)
And in a Script put this:
local Players = game:GetService("Players")
local see = game:GetService("ReplicatedStorage").PINActivate
local plr
-- replace Event with something like Part.Touched
Event:Connect(function(part)
if part.Parent.Head then
plr = Players:GetPlayerFromCharacter(part.Parent)
see:FireClient(plr)
end
end)
What this will do is bring up a ScreenGui for only that player so they can enter the PIN, and they can close it as well. You can modify as needed; have a great day! :D
There is an easier way, try this
First, Create a GUI in StarterGui, then, Create a textbox and postion it, after that, create a local script inside and type this.
local Password = math.random(1000, 9999)
print(Password)
game.ReplicatedStorage.Password.Value = Password
script.Parent.FocusLost:Connect(function(enter)
if enter then
if script.Parent.Text == tostring(Password) then
print("Correct!")
script.Parent.BorderColor3 = Color3.new(0, 255, 0)
Password = math.random(1000, 9999)
game.ReplicatedStorage.Correct1:FireServer()
print(Password)
game.ReplicatedStorage.Password.Value = Password
else
print("wrong!")
print(script.Parent.Text)
script.Parent.BorderColor3 = Color3.new(255, 0, 0)
end
end
end)
That's all in the textbox.
Or if you want a random username script, create a textlabel, then, create a local script in the textlabel and type in this.
local UserText = script.Parent
local Username = math.random(1,10)
while true do
if Username == 1 then
UserText.Text = "John"
elseif Username == 2 then
UserText.Text = "Thomas"
elseif Username == 3 then
UserText.Text = "Raymond"
elseif Username == 4 then
UserText.Text = "Ray"
elseif Username == 5 then
UserText.Text = "Tom"
elseif Username == 6 then
UserText.Text = "Kai"
elseif Username == 7 then
UserText.Text = "Lloyd"
elseif Username == 8 then
UserText.Text = "Jay"
elseif Username == 9 then
UserText.Text = "User"
else
UserText.Text = "Guest"
end
wait()
end
All of those if statments are checking what username has been chosen. I have made a roblox game like this recently, so I just took all the script from the game.
If you want to check out my game, Click Here

Why isn't .replace working in Ruby Shoes?

I am programming a little game based on the Fate RPG. When the dice are rolled, I want to replace a string with another string using .replace. I can get it to work in an isolated environment, but when I try to call the function from inside my program; it is as if Shoes is completely unaware of it.
Here is a simple example of how the function works that is executing correctly:
Shoes.app {
#push = button "Push me"
#note = para "Nothing pushed so far"
#push.click { #note.replace "Aha! Click!" }
}
And here is the relevant code from my game:
$results = para "Roll results go here.", :align => "center",
:margin_bottom => 20, :margin_top => 8
#roll_button.click {
current_roll = Die.new
current_roll.roll
current_roll.display_dice
current_roll.tally
current_roll.calc_total_roll(1) #param = skill level
$shift = current_roll.calc_total_shift(2) #param = opposition
$results.replace "Actual results"
}
The $results block is in a different position in the code than the #roll_button.click block, but I have tries moving the click block to many different places in the code, and it didn't make a difference, so I don't think its relevant. Thanks.
*edit: Removed unnecessary '=' after $results.replace
I finally got it to work. The problem was the .display_dice function running just before .replace. The offending code is here:
if $result1 == 1
$die1.path = "dice_plus-1.png"
elsif $result1 == 0
$die1.path = "dice_nil-1.png"
elsif $result1 == -1
$die1.path = "dice_minus-1.png"
else
exit(1)
end
I intended the exit(1) to let me know if my dice were receiving values they shouldn't, but it somehow prevented the next line of code from running, even though the flow of the program avoided those lines. The fixed code is here:
if $result1 == 1
$die1.path = "dice_plus-1.png"
elsif $result1 == 0
$die1.path = "dice_nil-1.png"
else $result1 == -1
$die1.path = "dice_minus-1.png"
end
You're not calling a replace method, you're calling a replace= method which probably doesn't exist. Try it without the equals sign.

how to generate endless random objects in corona SDK?

so I am very new to coding in general and I am trying to make a vertically-scrolling endless runner which basically involves jumping onto platforms to stay alive.I want to generate the same platform in three different locations endlessly. I basically copied some code from an article on the internet and then changed it around to try to make it suit my needs. However, when I run my code in the simulator, one platform is generated in the same location and no others appear. Also, when I look at the console, random numbers do appear. here is the code I am using
local blocks = display.newGroup ()
local groundMin = 200
local groundMax = 100
local groundLevel = groundMin
local function blockgenerate( event )
for a = 1, 1, -1 do
isDone = false
numGen = math.random(3)
local newBlock
print (numGen)
if (numGen == 1 and isDone == false) then
newBlock = display.newImage ("platform.jpg")
end
if (numGen == 2 and isDone == false) then
newBlock = display.newImage ("platform.jpg")
end
if (numGen == 3 and isDone == false) then
newBlock = display.newImage ("platform.jpg")
end
newBlock.name = ("block" .. a)
newBlock.id = a
newBlock.x = (a * 100) - 100
newBlock.y = groundLevel
blocks : insert(newBlock)
end
end
timer.performWithDelay (1000, blockgenerate, -1)
thank you very much in advance and sorry my description was so long
Your "a" variable is always going to be 1. Perhaps you meant to use:
a = a + 1

Resources