Tracks scores for players based on what they do in the world. Now you can see who really puts in the work!
Small changes concerning balance, gameplay, or graphics.
Hi! I'm the maintainer of Autodrive, which allows you to control vehicles remotely. Most of the time, vehicles are moved by script; players can be driver or passenger, but can't steer a vehicle that's in automatic driving mode.
Vehicles can be equipped with "sensors" for different situation. For example, we have "gate sensors" (if equipped, gates will automatically open so the vehicle can pass through) and "biter alert sensors" (if the vehicle has weapons, it will shoot at enemies within range while driving). These will work out of the box if at least one player is riding in the vehicle. If the vehicle is empty, we put a dummy character inside the vehicle for as long as it is necessary. The dummy characters are neither connected to nor associated with a player.
On your info page, I've found this statement:
kills while in the tank go to the passenger unless you run over the enemy or are the sole operator
This is bound to crash when your mod is used together with Autodrive (AAI Programmable Vehicles may have issues, too). In your handler for on_entity_died, I've found this:
addScore(awardee.player.name, settings.global["MultiplayerScoring_Killing_Value"].value)
where
awardee = e.cause.get_driver() or e.cause.get_passenger()
Now, entity.get_driver() and entity.get_passenger() may return either LuaEntity (a character prototype) or LuaPlayer (see here). You should make sure that you can get a LuaPlayer for awardee before giving it a score:
awardee = e.cause.get_driver()
if awardee then
-- awardee is a character
if awardee.object_name ~= "LuaPlayer" then
awardee = awardee.valid and awardee.player or awardee.associated_player
end
if awardee and awardee.valid then
addScore(awardee.name, settings.global["MultiplayerScoring_Killing_Value"].value)
end
end
Thanks I'll implement a fix for this potential bug. Done.
Thank you! However, there still is room for failure:
if not awardee then
return
elseif awardee.object_name == "LuaEntity" then
awardee = awardee.player
for _, player in pairs(game.players) do
if player.name == awardee.name then
The check for awardee.object_name will always work because object_name can be read even if the object is not valid. The next line (awardee = awardee.player) will crash if awardee is not valid. Even if awardee was valid, awardee.player could still be nil, so you'd get a crash (trying to index awardee, a nil value) when checking for "player.name == awardee.name".
Also, looping over all players seems wrong:
for _, player in pairs(game.players) do
if player.name == awardee.name then
local multiplier = math.ceil(e.entity.prototype.max_health / 100)
if e.entity.type == "unit" or e.entity.type == "turret" then
addScore(awardee.name, settings.global["MultiplayerScoring_Killing_Value"].value * multiplier)
elseif e.entity.type == "unit-spawner" then
addScore(awardee.name, settings.global["MultiplayerScoring_Nest_Value"].value * multiplier)
end
else
return
end
end
Suppose awardee is player 2. The loop will start with player 1. In this case, player.name is not the same as awardee.name, so you take the "else" branch and return -- and will never get to check player 2.
You always want to add the score for just one player. So why don't you check directly if such a player exists? You can index game.players using player.index or player.name, so something like this would be more efficient:
if awardee and awardee.valid and game.players[awardee.name] and game.players[awardee.name].valid then
-- Add score
else
return
end
Hrmmm. If there is no awardee it just returns, so there's no reason to validate it? If it returns an entity instead it just gets the player from the entity.
Yes the loop is very inefficient, but i couldn't find any reference to .valid in the API. is this a lua specific check?
On a side note. initially for the gui I was just updating the caption for each element, but for the life of me i couldn't get that to work properly with a dynamically ordered list. So now i'm destroying and recreating the table every time the score changes which is also very inefficient. Any ideas there?
Hrmmm. If there is no awardee it just returns, so there's no reason to validate it?
OK, you're right: Whatever get_driver() returns should be valid.
If it returns an entity instead it just gets the player from the entity.
And that's right back where we started: If Autodrive is used, it's possible that the entity is independent of any player, so entity.player will be nil. :-)
Yes the loop is very inefficient, but i couldn't find any reference to .valid in the API. is this a lua specific check?
It's a property really every single object (entity, GUI element, technology, etc.) has. You always can access object.valid and object.object_name, but you only can access any other property (name, type, index, get_driver(), color, etc.) if object.valid is true.
On a side note. initially for the gui I was just updating the caption for each element, but for the life of me i couldn't get that to work properly with a dynamically ordered list. So now i'm destroying and recreating the table every time the score changes which is also very inefficient. Any ideas there?
Yes:
local column_count = 2
local f = main.add({type = "table", name = "scoretable", style = "mod_info_table", column_count = column_count})
f.add({type = "label", caption = "Player", style = "bold_label"})
f.add({type = "label", caption = "Score", style = "bold_label"})
There's no way you can distinguish the labels -- why? LuaGuiElement.add allows for optional name or index. I'm currently working on a GUI with a dynamic table myself. So I've defined a table with names for all my GUI elements. This is the table for the columns of my table:
table_columns = {
-- Vehicle list
icon = p_vehicles_prefix.."vehicle_list_icon_",
proto_name = p_vehicles_prefix.."vehicle_list_prototype_name_",
id = p_vehicles_prefix.."vehicle_list_id_",
surface = p_vehicles_prefix.."vehicle_list_surface_",
position = p_vehicles_prefix.."vehicle_list_position_",
toggle_owned = p_vehicles_prefix.."vehicle_list_toggle_owned_",
toggle_lock = p_vehicles_prefix.."vehicle_list_toggle_lock_",
map_view = p_vehicles_prefix.."vehicle_list_toggle_map_",
}
When I add an entity to the table, it looks like this:
for v, vehicle in pairs(vehicles) do
v_id = vehicle.unit_number
-- Icon
if not vehicle_list[gui_names.table_columns.icon..v_id] then
element = vehicle_list.add({
type = "sprite",
name = gui_names.table_columns.icon..v_id,
sprite = "item/"..GCKI.vehicle_prototypes_map[vehicle.type][vehicle.name].placeable_by,
})
GCKI.created_msg(element)
end
-- Prototype name
if not vehicle_list[gui_names.table_columns.proto_name..v_id] then
element = vehicle_list.add({
type = "label",
name = gui_names.table_columns.proto_name..v_id,
caption = GCKI.loc_name(vehicle),
})
GCKI.created_msg(element)
end
…
end
So in the table, the column names will start with a fixed string and end with the entity's unit_number.
Now, it can happen that some other mod replaces entities I refer to. For example, AAI Programmable Vehicles will destroy a vehicle when it reaches a waypoint and then create a new one. It informs other mods via the remote interface when it has replaced an entity, so when AAI exchanges an entity, I update only the GUI elements for the old entity:
for c, column in pairs(column_names) do
old_name = column..old_id
new_name = column..new_id
if gui_table[old_name] and gui_table[old_name].valid then
gui_table[old_name].name = new_name
end
end
Similarly, to remove an entity, I use:
for v, vehicle in pairs(vehicles) do
id = vehicle.unit_number
-- Remove vehicle from GUI --
for c, column in pairs(columns) do
col_name = gui_names.table_columns[column]..id
if tab[col_name] and tab[col_name].valid then
tab[col_name].destroy()
end
end
If it returns an entity instead it just gets the player from the entity.
And that's right back where we started: If Autodrive is used, it's possible that the entity is independent of any player, so entity.player will be nil. :-)
Oh, yep. I missed that in my logic.
Yes the loop is very inefficient, but i couldn't find any reference to .valid in the API. is this a lua specific check?
It's a property really every single object (entity, GUI element, technology, etc.) has. You always can access object.valid and object.object_name, but you only can access any other property (name, type, index, get_driver(), color, etc.) if object.valid is true.
Oh so, I was trying to check if an entity was a ghost by doing if not entity.ghost_name but it errored with "entity is not a ghost". Could I have just done if not entity.ghost_name.valid ?
There's no way you can distinguish the labels -- why? LuaGuiElement.add allows for optional name or index. I'm currently working on a GUI with a dynamic table myself. So I've defined a table with names for all my GUI elements. This is the table for the columns of my table:
So to use the name parameter would require dynamic names, which I tried, but failed to do. The other issue I had with trying to update the caption is the key for the player name would always return nil, even though in the line before it had a value. spent 48 hours trying to debug just that one issue. gave up and rebuilt the table now.
Oh so, I was trying to check if an entity was a ghost by doing if not entity.ghost_name but it errored with "entity is not a ghost". Could I have just done if not entity.ghost_name.valid ?
The properties listed here are all properties of entities, but not all entities will have all properties. For example, entity.neighbours makes sense for the wire connections of power poles, or for a piece of rail track, but not for assemblers; or get_driver() makes only sense for vehicles, but not a chest. Likewise, you only can access entity.ghost_name if entity.valid and entity.type == "entity-ghost".
There's no way you can distinguish the labels -- why? LuaGuiElement.add allows for optional name or index. I'm currently working on a GUI with a dynamic table myself. So I've defined a table with names for all my GUI elements. This is the table for the columns of my table:
So to use the name parameter would require dynamic names, which I tried, but failed to do.
So, you've got a table
local rank = Utils.sortedKeys(player_score)
that looks like
{ [1] = "Player 3", [2] = "Player 1", [3] = "Player 2"}
Then you could create your table like this:
local column_count = 2
if not main["scoretable"] then
local f = main.add({type = "table", name = "scoretable", style = "mod_info_table", column_count = column_count})
f.add({type = "label", caption = "Player", style = "bold_label", name = "Player-header"})
f.add({type = "label", caption = "Score", style = "bold_label", name = "Score-header"})
end
local p, s
local tab = main["scoretable"]
for p_rank, p_name in pairs(rank) do
p = "Player-"..tostring(p_rank)
s = "Score-"..tostring(p_rank)
if not (tab[p] and tab[p].valid) then
tab.add({type = "label", name = p, caption = p_name, style = "bold_label"})
else
tab[p].caption = p_name
end
if not (tab[s] and tab[s].valid) then
tab.add({type = "label", name = s, caption = player_score[p_name], style = "bold_label"})
else
tab[s].caption = p_name
end
end
This will create new named labels or update existing labels, so it's no problem if a new player is added to the game. When a player is are permanently removed, you could remove the last line of the table, or clear the table and rebuild it. (By the way, destroy() is too radical -- clear() will keep the table itself, but remove all child elements, i.e. just the labels you've created.)
The other issue I had with trying to update the caption is the key for the player name would always return nil, even though in the line before it had a value. spent 48 hours trying to debug just that one issue. gave up and rebuilt the table now.
Hard to tell what's wrong there, without code to look at. I recommend to log all values that you set. Sometimes it's really something stupid, like a value has not the type you'd expected.
I'm going to refactor all of that when I have time. I appreciate the help!
You're welcome! (By the way, I've forgotten the parentheses in "if not (tab[p] and tab[p].valid)" and the other block, fixed that.)
Thanks, I have worked in your suggestions for the vehicles and some gui things. I still cannot get updating the captions to work. It seems to not want to recognize keys in the table when I try this even though it has nothing to do with the gui. I must be missing something.
P.S. Sent you a PM on Factorio forums.