Skip to content

Commit

Permalink
Add rankicon parameter to CLIENTSTATUS command
Browse files Browse the repository at this point in the history
  • Loading branch information
jauggy committed Apr 16, 2024
1 parent dca265a commit 52989e2
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 17 deletions.
2 changes: 1 addition & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ config :teiserver, Oban,
crontab: false

# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"
config :logger, :console, format: "[$level] $message\n", level: :info

config :logger,
backends: [
Expand Down
2 changes: 1 addition & 1 deletion lib/teiserver/account.ex
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ defmodule Teiserver.Account do
|> Repo.one()
end

@spec get_user_stat_data(integer()) :: Map.t()
@spec get_user_stat_data(integer()) :: map()
def get_user_stat_data(userid) do
Teiserver.cache_get_or_store(:teiserver_user_stat_cache, userid, fn ->
case get_user_stat(userid) do
Expand Down
2 changes: 1 addition & 1 deletion lib/teiserver/account/libs/role_lib.ex
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ defmodule Teiserver.Account.RoleLib do
end

def allowed_role_management("Moderator") do
global_roles() ++ moderation_roles() ++ property_roles()
global_roles() ++ moderation_roles() ++ property_roles() ++ community_roles()
end

def allowed_role_management(_) do
Expand Down
65 changes: 53 additions & 12 deletions lib/teiserver/data/cache_user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -1264,23 +1264,26 @@ defmodule Teiserver.CacheUser do
def is_verified?(%{roles: roles}), do: Enum.member?(roles, "Verified")
def is_verified?(_), do: false

@spec rank_time(T.userid()) :: non_neg_integer()
def rank_time(userid) do
@spec rank_time(T.userid() | map()) :: non_neg_integer()
def rank_time(userid) when is_integer(userid) do
stats = Account.get_user_stat(userid) || %{data: %{}}

rank_time(stats.data)
end

def rank_time(stats_data) when is_map(stats_data) do
ingame_minutes =
(stats.data["player_minutes"] || 0) + (stats.data["spectator_minutes"] || 0) * 0.5
(stats_data["player_minutes"] || 0) + (stats_data["spectator_minutes"] || 0) * 0.5

round(ingame_minutes / 60)
floor(ingame_minutes / 60)
end

# Based on actual ingame time
@spec calculate_rank(T.userid(), String.t()) :: non_neg_integer()
def calculate_rank(userid, "Playtime") do
ingame_hours = rank_time(userid)

[5, 15, 30, 100, 300, 1000, 3000]
|> Enum.count(fn r -> r <= ingame_hours end)
convert_hours_to_rank(ingame_hours)
end

# Using leaderboard rating
Expand All @@ -1306,12 +1309,7 @@ defmodule Teiserver.CacheUser do

cond do
has_any_role?(userid, ~w(Core Contributor)) -> 6
ingame_hours > 1000 -> 5
ingame_hours > 250 -> 4
ingame_hours > 100 -> 3
ingame_hours > 15 -> 2
ingame_hours > 5 -> 1
true -> 0
true -> convert_hours_to_rank(ingame_hours)
end
end

Expand All @@ -1321,6 +1319,49 @@ defmodule Teiserver.CacheUser do
calculate_rank(userid, method)
end

@doc """
This should match
https://www.beyondallreason.info/guide/rating-and-lobby-balance#rank-icons
However special ranks are ignored
"""
defp convert_hours_to_rank(hours) do
cond do
hours >= 1000 -> 5
hours >= 250 -> 4
hours >= 100 -> 3
hours >= 15 -> 2
hours >= 5 -> 1
true -> 0
end
end

def get_rank_icon(userid) do
stats = Account.get_user_stat_data(userid)
# Play time Rank
rank =
cond do
# This is only for tournament winners
stats["rank_override"] != nil ->
stats["rank_override"] |> int_parse

true ->
hours = rank_time(stats)
convert_hours_to_rank(hours)
end

%{roles: roles} = Account.get_user_by_id(userid)
chev_level = rank + 1

cond do
Enum.member?(roles, "Moderator") -> "#{chev_level}Moderator"
Enum.member?(roles, "Contributor") -> "#{chev_level}Contributor"
Enum.member?(roles, "Streamer") -> "#{chev_level}Streamer"
Enum.member?(roles, "Mentor") -> "#{chev_level}Mentor"
true -> "#{chev_level}Chev"

end
end

# Used to reset the spring password of the user when the site password is updated
def set_new_spring_password(userid, new_password) do
user = get_user_by_id(userid)
Expand Down
2 changes: 1 addition & 1 deletion lib/teiserver/libs/teiserver_configs.ex
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@ defmodule Teiserver.TeiserverConfigs do
key: "profile.Rank method",
section: "Profiles",
type: "select",
default: "Leaderboard rating",
default: "Role",
permissions: ["Admin"],
description: "The value used to assign rank icons at login",
opts: [choices: ["Leaderboard rating", "Rating value", "Playtime", "Role"]]
Expand Down
10 changes: 9 additions & 1 deletion lib/teiserver/protocols/spring/spring_out.ex
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,16 @@ defmodule Teiserver.Protocols.SpringOut do
defp do_reply(:client_status, nil), do: ""

defp do_reply(:client_status, client) do

rank_icon = cond do
client.lobby_client == "Teiserver Internal Client" -> "Bot"
client.bot -> "Bot"
true -> CacheUser.get_rank_icon(client.userid)
end


status = Spring.create_client_status(client)
"CLIENTSTATUS #{client.name} #{status}\n"
"CLIENTSTATUS #{client.name} #{status} #{rank_icon}\n"
end

defp do_reply(:client_battlestatus, nil), do: nil
Expand Down
196 changes: 196 additions & 0 deletions lib/test_util/test_script.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
defmodule TestScript do
@moduledoc """
This module is not used anywhere but it can be called while developing to create 4 users for testing:
Alpha, Bravo, Charlie, Delta
password is password
Run the server while enabling iex commands:
iex -S mix phx.server
TestScript.run()
"""
alias Teiserver.Helper.StylingHelper
require Logger
alias Teiserver.{Account, CacheUser}
alias Teiserver.Game.MatchRatingLib

def run() do
if Application.get_env(:teiserver, Teiserver)[:enable_hailstorm] do
# Start by rebuilding the database


users = [
%{
name: "1Chev",
player_minutes: 0,
os: 17
},
%{
name: "2Chev",
player_minutes: 5 * 60,
os: 17
},
%{
name: "3Chev",
player_minutes: 15 * 60,
os: 17
},
%{
name: "4Chev",
player_minutes: 100 * 60,
os: 17
},
%{
name: "5Chev",
player_minutes: 250 * 60,
os: 17
},
%{
name: "6Chev",
player_minutes: 1001 * 60,
os: 17
},
%{
name: "7Chev",
player_minutes: 1001 * 60,
os: 17,
rank_override: 6
},
%{
name: "8Chev",
player_minutes: 1001 * 60,
os: 17,
rank_override: 7
}
]

user_names = Enum.map(users, fn x -> x.name end)
make_accounts(user_names)
update_stats(users)

"Test script finished successfully"
else
Logger.error("Hailstorm mode is not enabled, you cannot run the fakedata task")
end
end

defp make_accounts(list_of_names) do
root_user = Teiserver.Repo.get_by(Teiserver.Account.User, email: "root@localhost")

fixed_users =
list_of_names
|> Enum.map(fn x -> make_user(x, root_user) end)
|> Enum.filter(fn x -> x != nil end)

Ecto.Multi.new()
|> Ecto.Multi.insert_all(:insert_all, Teiserver.Account.User, fixed_users)
|> Teiserver.Repo.transaction()
end

# root_user is used to copy password and hash
# returns nil if user exists
defp make_user(name, root_user, day \\ 0, minutes \\ 0) do
name = name |> String.replace(" ", "")

case Teiserver.Account.UserCacheLib.get_user_by_name(name) do
nil ->
%{
name: name,
email: "#{name}",
password: root_user.password,
permissions: ["admin.dev.developer"],
icon: "fa-solid #{StylingHelper.random_icon()}",
colour: StylingHelper.random_colour(),
trust_score: 10_000,
behaviour_score: 10_000,
roles: ["Verified"],
data: %{
lobby_client: "FakeData",
bot: false,
password_hash: root_user.data["password_hash"]
},
inserted_at: Timex.shift(Timex.now(), days: -day, minutes: -minutes) |> time_convert,
updated_at: Timex.shift(Timex.now(), days: -day, minutes: -minutes) |> time_convert
}

# Handle not nil
_ ->
Logger.info("#{name} already exists")
nil
end
end

# This allows us to round off microseconds and convert datetime to naive_datetime
defp time_convert(t) do
t
|> Timex.to_unix()
|> Timex.from_unix()
|> Timex.to_naive_datetime()
end

defp update_stats(users) when is_list(users) do
for user <- users, do: update_stats(user.name, user.player_minutes, user.os, user[:rank_override])
end

# Update the database with player minutes
def update_stats(username, player_minutes, os, rank_override \\ nil) do
user = Teiserver.Account.UserCacheLib.get_user_by_name(username)
user_id = user.id

user_stat = %{
player_minutes: player_minutes,
total_minutes: player_minutes
}

user_stat = cond do
rank_override != nil -> Map.put(user_stat, :rank_override, rank_override)
true -> user_stat
end

Account.update_user_stat(user_id, user_stat)

# Now recalculate ranks
# This calc would usually be done in do_login
rank = CacheUser.calculate_rank(user_id)

user = %{
user
| rank: rank
}

CacheUser.update_user(user, true)
update_rating(user.id, os)
end

defp update_rating(user_id, os) do
new_uncertainty = 6
new_skill = os + new_uncertainty
new_rating_value = os
new_leaderboard_rating = os - 2 * new_uncertainty
rating_type = "Team"
rating_type_id = MatchRatingLib.rating_type_name_lookup()[rating_type]

case Account.get_rating(user_id, rating_type_id) do
nil ->
Account.create_rating(%{
user_id: user_id,
rating_type_id: rating_type_id,
rating_value: new_rating_value,
skill: new_skill,
uncertainty: new_uncertainty,
leaderboard_rating: new_leaderboard_rating,
last_updated: Timex.now()
})

existing ->
Account.update_rating(existing, %{
rating_value: new_rating_value,
skill: new_skill,
uncertainty: new_uncertainty,
leaderboard_rating: new_leaderboard_rating,
last_updated: Timex.now()
})
end
end
end

0 comments on commit 52989e2

Please sign in to comment.