mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-16 08:55:18 +00:00
command experience calculations upon facility capture or resecure
This commit is contained in:
parent
8bdb215d58
commit
77066f962e
48 changed files with 1544 additions and 1020 deletions
|
|
@ -17,9 +17,10 @@ CREATE TABLE IF NOT EXISTS "sessionnumber" (
|
|||
|
||||
CREATE TABLE IF NOT EXISTS "killactivity" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"killer_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"victim_exosuit" SMALLINT NOT NULL,
|
||||
"victim_mounted" INT NOT NULL DEFAULT 0,
|
||||
"weapon_id" SMALLINT NOT NULL,
|
||||
"zone_id" SMALLINT NOT NULL,
|
||||
"px" INT NOT NULL,
|
||||
|
|
@ -33,7 +34,7 @@ CREATE TABLE IF NOT EXISTS "kda" (
|
|||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"kills" INT NOT NULL DEFAULT 0,
|
||||
"deaths" INT NOT NULL DEFAULT 0,
|
||||
"assists" INT NOT NULL DEFAULT 0,
|
||||
"revives" INT NOT NULL DEFAULT 0,
|
||||
UNIQUE(avatar_id)
|
||||
);
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ CREATE TABLE IF NOT EXISTS "kdasession" (
|
|||
"session_id" INT NOT NULL,
|
||||
"kills" INT NOT NULL DEFAULT 0,
|
||||
"deaths" INT NOT NULL DEFAULT 0,
|
||||
"assists" INT NOT NULL DEFAULT 0,
|
||||
"revives" INT NOT NULL DEFAULT 0,
|
||||
UNIQUE(avatar_id, session_id)
|
||||
);
|
||||
|
||||
|
|
@ -73,89 +74,144 @@ CREATE TABLE IF NOT EXISTS "legacykills" (
|
|||
UNIQUE(avatar_id)
|
||||
);
|
||||
|
||||
CREATE VIEW kdacampaign AS (
|
||||
SELECT
|
||||
session.avatar_id,
|
||||
SUM(session.kills) AS kills,
|
||||
SUM(session.deaths) AS deaths,
|
||||
SUM(session.assists) AS assists,
|
||||
COUNT(session.avatar_id) AS numberOfSessions
|
||||
FROM (
|
||||
SELECT avatar_id, session_id, kills, deaths, assists
|
||||
FROM kdasession
|
||||
UNION ALL
|
||||
SELECT avatar_id, 0, kills, deaths, assists
|
||||
FROM kda
|
||||
) AS session
|
||||
LEFT JOIN kda
|
||||
ON kda.avatar_id = session.avatar_id
|
||||
GROUP BY session.avatar_id
|
||||
CREATE TABLE IF NOT EXISTS "machinedestroyed" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"weapon_id" INT NOT NULL,
|
||||
"machine_type" INT NOT NULL,
|
||||
"machine_faction" SMALLINT NOT NULL,
|
||||
"hacked_faction" SMALLINT NOT NULL,
|
||||
"as_cargo" BOOLEAN NOT NULL,
|
||||
"zone_num" SMALLINT NOT NULL,
|
||||
"px" INT NOT NULL,
|
||||
"py" INT NOT NULL,
|
||||
"pz" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE VIEW weaponstatcampaign AS (
|
||||
SELECT
|
||||
weaponstat.avatar_id,
|
||||
weaponstat.weapon_id,
|
||||
SUM(session.shots_fired) AS shots_fired,
|
||||
SUM(session.shots_landed) AS shots_landed,
|
||||
SUM(session.kills) AS kills,
|
||||
SUM(session.assists) AS assists,
|
||||
COUNT(session.session_id) AS numberOfSessions
|
||||
FROM (
|
||||
SELECT avatar_id, weapon_id, session_id, shots_fired, shots_landed, kills, assists
|
||||
FROM weaponstatsession
|
||||
UNION ALL
|
||||
SELECT avatar_id, weapon_id, 0, shots_fired, shots_landed, kills, assists
|
||||
FROM weaponstat
|
||||
) AS session
|
||||
LEFT JOIN weaponstat
|
||||
ON weaponstat.avatar_id = session.avatar_id
|
||||
GROUP BY weaponstat.avatar_id, weaponstat.weapon_id
|
||||
CREATE TABLE IF NOT EXISTS "assistactivity" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"attacker_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"weapon_id" SMALLINT NOT NULL,
|
||||
"zone_id" SMALLINT NOT NULL,
|
||||
"px" INT NOT NULL,
|
||||
"py" INT NOT NULL,
|
||||
"pz" INT NOT NULL,
|
||||
"exp" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
/*
|
||||
Procedure for initializing and increasing the session number.
|
||||
Always indexes a new session.
|
||||
*/
|
||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncrease
|
||||
(avatarId IN Int, number OUT Int)
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
SELECT (MAX(session_id,0)+1) INTO number
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId;
|
||||
INSERT INTO sessionnumber
|
||||
VALUES (avatarId, number);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TABLE IF NOT EXISTS "supportactivity" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
|
||||
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
|
||||
"target_exosuit" SMALLINT NOT NULL, -- benefactor's exo-suit
|
||||
"interaction_type" SMALLINT NOT NULL, -- classification of support
|
||||
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
|
||||
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
|
||||
"exp" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "ntuactivity" (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"zone_id" INT NOT NULL,
|
||||
"building_id" INT NOT NULL,
|
||||
"exp" INT NOT NULL,
|
||||
UNIQUE(avatar_id, zone_id, building_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "buildingcapture" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"zone_id" INT NOT NULL,
|
||||
"building_id" INT NOT NULL,
|
||||
"exp" INT NOT NULL,
|
||||
"exp_type" CHAR(3) NOT NULL, /* bep, cep, llu */
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "expbuildingcapture" (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"zone_id" INT NOT NULL,
|
||||
"building_id" INT NOT NULL,
|
||||
"captures" INT NOT NULL,
|
||||
"bep" INT NOT NULL,
|
||||
"cep" INT NOT NULL,
|
||||
UNIQUE(avatar_id, zone_id, building_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "llubuildingcapture" (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"zone_id" INT NOT NULL,
|
||||
"building_id" INT NOT NULL,
|
||||
"captures" INT NOT NULL,
|
||||
"exp" INT NOT NULL,
|
||||
UNIQUE(avatar_id, zone_id, building_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "progressiondebt" (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"experience" INT NOT NULL DEFAULT 0,
|
||||
UNIQUE(avatar_id)
|
||||
);
|
||||
|
||||
/*
|
||||
Procedure for initializing and increasing the session number.
|
||||
Index for a new session only if last session was created more than one hour ago.
|
||||
*/
|
||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncreasePerHour
|
||||
(avatarId IN Int, number OUT Int, nextNumber OUT Int)
|
||||
CREATE OR REPLACE FUNCTION proc_sessionnumber_test
|
||||
(avatarId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE time TIMESTAMP;
|
||||
DECLARE number integer;
|
||||
BEGIN
|
||||
SELECT MAX(session_id,0) INTO number
|
||||
SELECT MAX(session_id) INTO number
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId;
|
||||
SELECT timestamp INTO time
|
||||
SELECT COALESCE(timestamp) INTO time
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId AND session_id = number;
|
||||
IF (CAST(CURRENT_TIMESTAMP AS FLOAT) > CAST(DATE_ADD('hour', 1, DATE_TRUNC('hour', time)) AS FLOAT)) THEN
|
||||
IF (time IS null) THEN
|
||||
number := 0;
|
||||
END IF;
|
||||
RETURN number;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION proc_sessionnumber_initAndOrIncreasePerHour
|
||||
(avatarId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE time TIMESTAMP;
|
||||
DECLARE number integer;
|
||||
DECLARE nextNumber integer;
|
||||
BEGIN
|
||||
SELECT MAX(session_id) INTO number
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId;
|
||||
SELECT COALESCE(timestamp) INTO time
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId AND session_id = number;
|
||||
IF (time IS null) THEN
|
||||
nextNumber := 1;
|
||||
INSERT INTO sessionnumber
|
||||
VALUES (avatarId, nextNumber);
|
||||
ELSIF (CURRENT_TIMESTAMP > DATE_TRUNC('hour', time) + interval '1' hour) THEN
|
||||
nextNumber := number + 1;
|
||||
INSERT INTO sessionnumber
|
||||
VALUES (avatarId, number);
|
||||
VALUES (avatarId, nextNumber);
|
||||
ELSE
|
||||
nextNumber := number;
|
||||
UPDATE sessionnumber
|
||||
SET timestamp = CURRENT_TIMESTAMP
|
||||
WHERE avatar_id = avatarId AND session_id = number;
|
||||
END IF;
|
||||
RETURN nextNumber;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -163,24 +219,35 @@ $$ LANGUAGE plpgsql;
|
|||
Procedure for accessing any existing session number.
|
||||
Actually polls the previous session number from the account table.
|
||||
*/
|
||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_get
|
||||
(avatarId IN Int, number OUT Int)
|
||||
CREATE OR REPLACE FUNCTION proc_sessionnumber_get
|
||||
(avatarId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE sessionId integer;
|
||||
DECLARE number integer;
|
||||
BEGIN
|
||||
SELECT COALESCE(session_id,0) INTO number
|
||||
SELECT COALESCE(session_id) INTO sessionId
|
||||
FROM account
|
||||
WHERE avatar_logged_in = avatarId;
|
||||
IF sessionId IS NULL THEN
|
||||
number := 0;
|
||||
ELSE
|
||||
number := sessionId;
|
||||
END IF;
|
||||
RETURN number;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
/*
|
||||
Procedures for ensuring that row entries can be found in specified tables before some other DML operation updates those row entries.
|
||||
*/
|
||||
CREATE OR REPLACE PROCEDURE proc_kda_addEntryIfNone
|
||||
(avatarId IN Int)
|
||||
CREATE OR REPLACE FUNCTION proc_kda_addEntryIfNone
|
||||
(avatarId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
IF EXISTS(
|
||||
SELECT *
|
||||
|
|
@ -189,43 +256,115 @@ BEGIN
|
|||
HAVING COUNT(*) = 0) THEN
|
||||
INSERT INTO kda (avatar_id)
|
||||
VALUES (avatarId);
|
||||
out := 1;
|
||||
ELSE
|
||||
out := 0;
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_weaponstat_addEntryIfNone
|
||||
(avatarId IN Int, weaponId IN Int)
|
||||
CREATE OR REPLACE FUNCTION proc_weaponstat_addEntryIfNone
|
||||
(avatarId integer, weaponId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE sessionId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
SELECT proc_sessionnumber_get(avatarId, sessionId);
|
||||
IF EXISTS(
|
||||
SELECT *
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, weapon_id
|
||||
FROM weaponstat
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
||||
HAVING COUNT(*) = 0) THEN
|
||||
INSERT INTO weaponstat (avatar_id, session_id, weapon_id)
|
||||
VALUES (avatarId, sessionId, weaponId);
|
||||
GROUP BY avatar_id, weapon_id
|
||||
) THEN
|
||||
INSERT INTO weaponstat (avatar_id, weapon_id)
|
||||
VALUES (avatarId, weaponId);
|
||||
out := 1;
|
||||
ELSE
|
||||
out := 0;
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNone
|
||||
(avatarId IN Int, weaponId IN Int)
|
||||
CREATE OR REPLACE FUNCTION proc_weaponstatsession_addEntryIfNoneWithSessionId
|
||||
(avatarId integer, weaponId integer, sessionId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, session_id, weapon_id
|
||||
FROM weaponstatsession
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId AND session_id = sessionId
|
||||
GROUP BY avatar_id, session_id, weapon_id
|
||||
) THEN
|
||||
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
||||
VALUES (avatarId, sessionId, weaponId);
|
||||
out := 1;
|
||||
ELSE
|
||||
out := 0;
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION proc_weaponstatsession_addEntryIfNone
|
||||
(avatarId integer, weaponId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE sessionId Int;
|
||||
BEGIN
|
||||
SELECT proc_sessionnumber_get(avatarId, sessionId);
|
||||
IF EXISTS(
|
||||
SELECT *
|
||||
FROM weaponstatsession
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
||||
HAVING COUNT(*) = 0) THEN
|
||||
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
||||
VALUES (avatarId, sessionId, weaponId);
|
||||
sessionId := proc_sessionnumber_get(avatarId);
|
||||
RETURN proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, weaponId, sessionId);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION proc_expbuildingcapture_addEntryIfNone
|
||||
(avatarId integer, buildingId integer, zoneId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, building_id, zone_id
|
||||
FROM expbuildingcapture
|
||||
WHERE avatar_id = avatarId AND building_id = buildingId AND zone_id = zoneId
|
||||
GROUP BY avatar_id, building_id, zone_id
|
||||
) THEN
|
||||
INSERT INTO expbuildingcapture (avatar_id, building_id, zone_id, captures, bep, cep)
|
||||
VALUES (avatarId, building_id, zone_id, 0, 0, 0);
|
||||
out := 1;
|
||||
ELSE
|
||||
out := 0;
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION proc_llubuildingcapture_addEntryIfNone
|
||||
(avatarId integer, buildingId integer, zoneId integer)
|
||||
RETURNS integer
|
||||
AS
|
||||
$$
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, building_id, zone_id
|
||||
FROM llubuildingcapture
|
||||
WHERE avatar_id = avatarId AND building_id = buildingId AND zone_id = zoneId
|
||||
GROUP BY avatar_id, building_id, zone_id
|
||||
) THEN
|
||||
INSERT INTO llubuildingcapture (avatar_id, building_id, zone_id, captures, exp)
|
||||
VALUES (avatarId, building_id, zone_id, 0, 0);
|
||||
out := 1;
|
||||
ELSE
|
||||
out := 0;
|
||||
END IF;
|
||||
RETURN out;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -243,11 +382,12 @@ DECLARE sessionId Int;
|
|||
DECLARE oldSessionId Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_logged_in;
|
||||
SELECT proc_sessionnumber_initAndOrIncreasePerHour(avatarId, oldSessionId, sessionId);
|
||||
oldSessionId := proc_sessionnumber_test(avatarId);
|
||||
sessionId := proc_sessionnumber_initAndOrIncreasePerHour(avatarId);
|
||||
IF (sessionId > oldSessionId) THEN
|
||||
BEGIN
|
||||
UPDATE account
|
||||
SET sessionId = sessionId
|
||||
SET session_id = sessionId
|
||||
WHERE id = OLD.id;
|
||||
INSERT INTO kdasession (avatar_id, session_id)
|
||||
VALUES (avatarId, sessionId);
|
||||
|
|
@ -258,7 +398,7 @@ END;
|
|||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_account_newSession
|
||||
BEFORE UPDATE
|
||||
AFTER UPDATE
|
||||
OF avatar_logged_in
|
||||
ON account
|
||||
FOR EACH ROW
|
||||
|
|
@ -281,12 +421,13 @@ DECLARE victimSessionId Int;
|
|||
DECLARE killerId Int;
|
||||
DECLARE victimId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
killerId := NEW.killer_id;
|
||||
victimId := NEW.victim_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
SELECT proc_sessionnumber_get(killerId, killerSessionId);
|
||||
SELECT proc_sessionnumber_get(victimId, victimSessionId);
|
||||
killerSessionId := proc_sessionnumber_get(killerId);
|
||||
victimSessionId := proc_sessionnumber_get(victimId);
|
||||
BEGIN
|
||||
UPDATE kdasession
|
||||
SET kills = kills + 1
|
||||
|
|
@ -298,6 +439,7 @@ BEGIN
|
|||
WHERE avatar_id = victimId AND session_id = victimSessionId;
|
||||
END;
|
||||
BEGIN
|
||||
out := proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, weaponId, killerSessionId);
|
||||
UPDATE weaponstatsession
|
||||
SET kills = kills + 1
|
||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||
|
|
@ -323,10 +465,12 @@ AS
|
|||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
SELECT proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
||||
out := proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -346,9 +490,11 @@ RETURNS TRIGGER
|
|||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
SELECT proc_kda_addEntryIfNone(avatarId);
|
||||
out := proc_kda_addEntryIfNone(avatarId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -369,10 +515,12 @@ AS
|
|||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
SELECT proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||
out := proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -382,10 +530,40 @@ ON weaponstat
|
|||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_weaponstat_addEntryIfNone();
|
||||
|
||||
/*
|
||||
Before attempting to update the revival data in a character's session kda column,
|
||||
set the session id to the current value then
|
||||
manually update the column, overriding the process.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_kdasession_updateRevives()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE sessionId Int;
|
||||
DECLARE newRevives Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
sessionId := proc_sessionnumber_get(avatarId);
|
||||
newRevives := NEW.revives;
|
||||
if (newRevives > 0) THEN
|
||||
UPDATE kdasession
|
||||
SET revives = revives + newRevives
|
||||
WHERE avatar_id = avatarId AND session_id = sessionId;
|
||||
END IF;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_kdasession_updateRevives
|
||||
BEFORE INSERT
|
||||
ON kdasession
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_kdasession_updateRevives();
|
||||
|
||||
/*
|
||||
Upon deletion of row entries for a character's session KDA,
|
||||
the values are copied over to the campaign KDA record for that character.
|
||||
This will fire mainly when called by the trigger for login (above).
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
|
||||
RETURNS TRIGGER
|
||||
|
|
@ -394,16 +572,18 @@ $$
|
|||
DECLARE avatarId Int;
|
||||
DECLARE oldKills Int;
|
||||
DECLARE oldDeaths Int;
|
||||
DECLARE oldAssists Int;
|
||||
DECLARE oldRevives Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := OLD.avatar_id;
|
||||
oldKills := OLD.kills;
|
||||
oldDeaths := OLD.deaths;
|
||||
oldAssists := OLD.assists;
|
||||
oldRevives := OLD.Revives;
|
||||
out := proc_kda_addEntryIfNone(avatarId);
|
||||
UPDATE kda
|
||||
SET kills = kills + oldKills,
|
||||
deaths = deaths + oldDeaths,
|
||||
assists = assists + oldAssists
|
||||
revives = revives + oldRevives
|
||||
WHERE avatar_id = avatarId;
|
||||
RETURN OLD;
|
||||
END;
|
||||
|
|
@ -418,7 +598,6 @@ EXECUTE FUNCTION fn_kdasession_updateOnDelete();
|
|||
/*
|
||||
Upon deletion of row entries for a character's session weapon stats,
|
||||
the values are copied over to the campaign weapon stats record for that character.
|
||||
This will fire mainly when called by the trigger for login (above).
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
|
||||
RETURNS TRIGGER
|
||||
|
|
@ -430,6 +609,7 @@ DECLARE oldKills Int;
|
|||
DECLARE oldAssists Int;
|
||||
DECLARE oldFired Int;
|
||||
DECLARE oldLanded Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := OLD.avatar_id;
|
||||
weaponId := OLD.weapon_id;
|
||||
|
|
@ -437,12 +617,14 @@ BEGIN
|
|||
oldAssists := OLD.assists;
|
||||
oldFired := OLD.shots_fired;
|
||||
oldLanded := OLD.shots_landed;
|
||||
out := proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||
UPDATE weaponstat
|
||||
SET kills = kills + oldKills,
|
||||
assists = assists + oldAssists,
|
||||
shots_fired = shots_fired + oldFired,
|
||||
shots_landed = shots_landed + oldLanded
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
|
|
@ -451,3 +633,152 @@ BEFORE DELETE
|
|||
ON weaponstatsession
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_weaponstatsession_updateOnDelete();
|
||||
|
||||
/*
|
||||
Before inserting a value into the weaponstatsession table to session id -1,
|
||||
correct the session id to the most current session id,
|
||||
and invalidate the attempted insertion at that -1 session id.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_beforeInsert()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE sessionId Int;
|
||||
DECLARE oldAssists Int;
|
||||
DECLARE oldFired Int;
|
||||
DECLARE oldLanded Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
oldAssists := NEW.assists;
|
||||
oldFired := NEW.shots_fired;
|
||||
oldLanded := NEW.shots_landed;
|
||||
sessionId := proc_sessionnumber_get(avatarId);
|
||||
out := proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, weaponId, sessionId);
|
||||
BEGIN
|
||||
UPDATE weaponstatsession
|
||||
SET
|
||||
assists = assists + oldAssists,
|
||||
shots_fired = shots_fired + oldFired,
|
||||
shots_landed = shots_landed + oldLanded
|
||||
WHERE avatar_id = avatarId AND session_id = sessionId AND weapon_id = weaponId;
|
||||
END;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_weaponstatsession_beforeInsert
|
||||
BEFORE INSERT
|
||||
ON weaponstatsession
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.session_id = -1)
|
||||
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();
|
||||
|
||||
/*
|
||||
A kill assist activity causes a major update to weapon stats:
|
||||
the weapon that was used in the activity has the kills count for the killer updated/increased.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE killerSessionId Int;
|
||||
DECLARE killerId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
killerId := NEW.killer_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
killerSessionId := proc_sessionnumber_get(killerId);
|
||||
out := proc_weaponstatsession_addEntryIfNone(killerId, killerSessionId, weaponId);
|
||||
BEGIN
|
||||
UPDATE weaponstatsession
|
||||
SET assists = assists + 1
|
||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_assistactivity_updateRelatedStats
|
||||
AFTER INSERT
|
||||
ON killactivity
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_assistactivity_updateRelatedStats();
|
||||
|
||||
/*
|
||||
Upon deletion of row entries for a character's building capture table,
|
||||
the values are copied over to a total capture record for that character.
|
||||
This will sort the values into one of two tables, and,
|
||||
for one table, it will add to either one column or to a different column.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_buildingcapture_updateOnDelete()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE oldAvatarId Int;
|
||||
DECLARE oldZoneId Int;
|
||||
DECLARE oldBuildingId Int;
|
||||
DECLARE oldExp Int;
|
||||
DECLARE oldType CHAR(3);
|
||||
DECLARE out integer;
|
||||
BEGIN
|
||||
oldAvatarId := NEW.avatar_id;
|
||||
oldZoneId := NEW.zone_id;
|
||||
oldBuildingId := NEW.building_id;
|
||||
oldExp := NEW.exp;
|
||||
oldType := NEW.exp_type;
|
||||
BEGIN
|
||||
IF (oldType LIKE "bep") THEN
|
||||
out := proc_expbuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||
UPDATE expbuildingcapture
|
||||
SET bep = bep + oldExp
|
||||
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||
ELSIF (oldType LIKE "cep") THEN
|
||||
out := proc_expbuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||
UPDATE expbuildingcapture
|
||||
SET cep = cep + oldExp
|
||||
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||
ELSIF (oldType LIKE "llu") THEN
|
||||
out := proc_llubuildingcapture_addEntryIfNone(avatarId, buildingId, zoneId);
|
||||
UPDATE llubuildingcapture
|
||||
SET exp = exp + oldExp
|
||||
WHERE avatar_id = oldAvatarId AND zone_id = oldZOneId AND building_id = oldBuildingId;
|
||||
END IF;
|
||||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_buildingcapture_updateOnDelete
|
||||
BEFORE DELETE
|
||||
ON buildingcapture
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_buildingcapture_updateOnDelete();
|
||||
|
||||
/*
|
||||
If a new avatar is created, a corresponding progression debt entry is also created by default.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_avatar_addDebtEntry()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE newAvatarId Int;
|
||||
BEGIN
|
||||
newAvatarId := NEW.avatar_id;
|
||||
BEGIN
|
||||
INSERT INTO progressiondebt (avatar_id)
|
||||
VALUES (newAvatarId);
|
||||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_avatar_addDebtEntry
|
||||
AFTER INSERT
|
||||
ON avatar
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_avatar_addDebtEntry();
|
||||
|
|
|
|||
|
|
@ -1,380 +0,0 @@
|
|||
/* changes to objects from V008__Scoring.sql */
|
||||
ALTER TABLE killactivity
|
||||
ADD COLUMN victim_mounted INT NOT NULL DEFAULT 0;
|
||||
|
||||
DROP PROCEDURE IF EXISTS proc_sessionnumber_initAndOrIncrease;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_initAndOrIncreasePerHour
|
||||
(avatarId IN Int, number OUT Int, nextNumber OUT Int)
|
||||
AS
|
||||
$$
|
||||
DECLARE time TIMESTAMP;
|
||||
BEGIN
|
||||
SELECT MAX(session_id) INTO number
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId;
|
||||
SELECT COALESCE(timestamp) INTO time
|
||||
FROM sessionnumber
|
||||
WHERE avatar_id = avatarId AND session_id = number;
|
||||
IF (time IS null) THEN
|
||||
number := 0;
|
||||
nextNumber := 1;
|
||||
INSERT INTO sessionnumber
|
||||
VALUES (avatarId, nextNumber);
|
||||
ELSIF (CURRENT_TIMESTAMP > DATE_TRUNC('hour', time) + interval '1' hour) THEN
|
||||
nextNumber := number + 1;
|
||||
INSERT INTO sessionnumber
|
||||
VALUES (avatarId, nextNumber);
|
||||
ELSE
|
||||
nextNumber := number;
|
||||
UPDATE sessionnumber
|
||||
SET timestamp = CURRENT_TIMESTAMP
|
||||
WHERE avatar_id = avatarId AND session_id = number;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_sessionnumber_get
|
||||
(avatarId IN Int, number OUT Int)
|
||||
AS
|
||||
$$
|
||||
DECLARE sessionId Int;
|
||||
BEGIN
|
||||
SELECT COALESCE(session_id) INTO sessionId
|
||||
FROM account
|
||||
WHERE avatar_logged_in = avatarId;
|
||||
IF sessionId IS NULL THEN
|
||||
number := 0;
|
||||
ELSE
|
||||
number := sessionId;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_weaponstat_addEntryIfNone
|
||||
(avatarId IN Int, weaponId IN Int)
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, weapon_id
|
||||
FROM weaponstat
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId
|
||||
GROUP BY avatar_id, weapon_id
|
||||
) THEN
|
||||
INSERT INTO weaponstat (avatar_id, weapon_id)
|
||||
VALUES (avatarId, weaponId);
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNoneWithSessionId
|
||||
(avatarId IN Int, sessionId IN Int, weaponId IN Int)
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
IF NOT EXISTS(
|
||||
SELECT avatar_id, session_id, weapon_id
|
||||
FROM weaponstatsession
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId AND session_id = sessionId
|
||||
GROUP BY avatar_id, session_id, weapon_id
|
||||
) THEN
|
||||
INSERT INTO weaponstatsession (avatar_id, session_id, weapon_id)
|
||||
VALUES (avatarId, sessionId, weaponId);
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE proc_weaponstatsession_addEntryIfNone
|
||||
(avatarId IN Int, weaponId IN Int)
|
||||
AS
|
||||
$$
|
||||
DECLARE sessionId Int;
|
||||
BEGIN
|
||||
CALL proc_sessionnumber_get(avatarId, sessionId);
|
||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_account_newSession()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE sessionId Int;
|
||||
DECLARE oldSessionId Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_logged_in;
|
||||
CALL proc_sessionnumber_initAndOrIncreasePerHour(avatarId, oldSessionId, sessionId);
|
||||
IF (sessionId > oldSessionId) THEN
|
||||
BEGIN
|
||||
UPDATE account
|
||||
SET session_id = sessionId
|
||||
WHERE id = OLD.id;
|
||||
INSERT INTO kdasession (avatar_id, session_id)
|
||||
VALUES (avatarId, sessionId);
|
||||
END;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_account_newSession
|
||||
AFTER UPDATE
|
||||
OF avatar_logged_in
|
||||
ON account
|
||||
FOR EACH ROW
|
||||
WHEN (OLD.avatar_logged_in = 0 AND NEW.avatar_logged_in > 0)
|
||||
EXECUTE FUNCTION fn_account_newSession();
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_killactivity_updateRelatedStats()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE killerSessionId Int;
|
||||
DECLARE victimSessionId Int;
|
||||
DECLARE killerId Int;
|
||||
DECLARE victimId Int;
|
||||
DECLARE weaponId Int;
|
||||
BEGIN
|
||||
killerId := NEW.killer_id;
|
||||
victimId := NEW.victim_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
CALL proc_sessionnumber_get(killerId, killerSessionId);
|
||||
CALL proc_sessionnumber_get(victimId, victimSessionId);
|
||||
BEGIN
|
||||
UPDATE kdasession
|
||||
SET kills = kills + 1
|
||||
WHERE avatar_id = killerId AND session_id = killerSessionId;
|
||||
END;
|
||||
BEGIN
|
||||
UPDATE kdasession
|
||||
SET deaths = deaths + 1
|
||||
WHERE avatar_id = victimId AND session_id = victimSessionId;
|
||||
END;
|
||||
BEGIN
|
||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(killerId, killerSessionId, weaponId);
|
||||
UPDATE weaponstatsession
|
||||
SET kills = kills + 1
|
||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_addEntryIfNone()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
CALL proc_weaponstatsession_addEntryIfNone(avatarId, weaponId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_kda_addEntryIfNone()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
CALL proc_kda_addEntryIfNone(avatarId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstat_addEntryIfNone()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_kdasession_updateOnDelete()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE oldKills Int;
|
||||
DECLARE oldDeaths Int;
|
||||
DECLARE oldAssists Int;
|
||||
BEGIN
|
||||
avatarId := OLD.avatar_id;
|
||||
oldKills := OLD.kills;
|
||||
oldDeaths := OLD.deaths;
|
||||
oldAssists := OLD.assists;
|
||||
CALL proc_kda_addEntryIfNone(avatarId);
|
||||
UPDATE kda
|
||||
SET kills = kills + oldKills,
|
||||
deaths = deaths + oldDeaths,
|
||||
assists = assists + oldAssists
|
||||
WHERE avatar_id = avatarId;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_updateOnDelete()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE oldKills Int;
|
||||
DECLARE oldAssists Int;
|
||||
DECLARE oldFired Int;
|
||||
DECLARE oldLanded Int;
|
||||
BEGIN
|
||||
avatarId := OLD.avatar_id;
|
||||
weaponId := OLD.weapon_id;
|
||||
oldKills := OLD.kills;
|
||||
oldAssists := OLD.assists;
|
||||
oldFired := OLD.shots_fired;
|
||||
oldLanded := OLD.shots_landed;
|
||||
CALL proc_weaponstat_addEntryIfNone(avatarId, weaponId);
|
||||
UPDATE weaponstat
|
||||
SET kills = kills + oldKills,
|
||||
assists = assists + oldAssists,
|
||||
shots_fired = shots_fired + oldFired,
|
||||
shots_landed = shots_landed + oldLanded
|
||||
WHERE avatar_id = avatarId AND weapon_id = weaponId;
|
||||
RETURN OLD;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
/* new objects */
|
||||
CREATE TABLE IF NOT EXISTS machinedestroyed (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"weapon_id" INT NOT NULL,
|
||||
"machine_type" INT NOT NULL,
|
||||
"machine_faction" SMALLINT NOT NULL,
|
||||
"hacked_faction" SMALLINT NOT NULL,
|
||||
"as_cargo" BOOLEAN NOT NULL,
|
||||
"zone_num" SMALLINT NOT NULL,
|
||||
"px" INT NOT NULL,
|
||||
"py" INT NOT NULL,
|
||||
"pz" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "assistactivity" (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"victim_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"attacker_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"weapon_id" SMALLINT NOT NULL,
|
||||
"zone_id" SMALLINT NOT NULL,
|
||||
"px" INT NOT NULL,
|
||||
"py" INT NOT NULL,
|
||||
"pz" INT NOT NULL,
|
||||
"exp" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS supportactivity (
|
||||
"index" SERIAL PRIMARY KEY NOT NULL,
|
||||
"user_id" INT NOT NULL REFERENCES avatar (id), -- player that provides the support
|
||||
"target_id" INT NOT NULL REFERENCES avatar (id), -- benefactor of the support
|
||||
"target_exosuit" SMALLINT NOT NULL, -- benefactor's exo-suit
|
||||
"interaction_type" SMALLINT NOT NULL, -- classification of support
|
||||
"intermediate_type" INT DEFAULT 0, -- through what medium user_id supports target_id
|
||||
"implement_type" INT DEFAULT 0, -- tool utilized by user_id to support target_id, potentially via interaction with intermediate_type
|
||||
"exp" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS respawnsession (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"session_id" INT NOT NULL,
|
||||
"respawn_count" INT NOT NULL DEFAULT 0,
|
||||
UNIQUE(avatar_id, session_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS respawn (
|
||||
"avatar_id" INT NOT NULL REFERENCES avatar (id),
|
||||
"respawn_count" INT NOT NULL DEFAULT 0,
|
||||
UNIQUE(avatar_id)
|
||||
);
|
||||
|
||||
/*
|
||||
Before inserting a value into the weaponstatsession table to session id -1,
|
||||
correct the session id to the most current session id,
|
||||
and invalidate the attempted insertion at that -1 session id.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_weaponstatsession_beforeInsert()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE avatarId Int;
|
||||
DECLARE weaponId Int;
|
||||
DECLARE sessionId Int;
|
||||
DECLARE oldAssists Int;
|
||||
DECLARE oldFired Int;
|
||||
DECLARE oldLanded Int;
|
||||
BEGIN
|
||||
avatarId := NEW.avatar_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
oldAssists := NEW.assists;
|
||||
oldFired := NEW.shots_fired;
|
||||
oldLanded := NEW.shots_landed;
|
||||
CALL proc_sessionnumber_get(avatarId, sessionId);
|
||||
CALL proc_weaponstatsession_addEntryIfNoneWithSessionId(avatarId, sessionId, weaponId);
|
||||
BEGIN
|
||||
UPDATE weaponstatsession
|
||||
SET
|
||||
assists = assists + oldAssists,
|
||||
shots_fired = shots_fired + oldFired,
|
||||
shots_landed = shots_landed + oldLanded
|
||||
WHERE avatar_id = avatarId AND session_id = sessionId AND weapon_id = weaponId;
|
||||
END;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_weaponstatsession_beforeInsert
|
||||
BEFORE INSERT
|
||||
ON weaponstatsession
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.session_id = -1)
|
||||
EXECUTE FUNCTION fn_weaponstatsession_beforeInsert();
|
||||
|
||||
/*
|
||||
A kill assist activity causes a major update to weapon stats:
|
||||
the weapon that was used in the activity has the kills count for the killer updated/increased.
|
||||
*/
|
||||
CREATE OR REPLACE FUNCTION fn_assistactivity_updateRelatedStats()
|
||||
RETURNS TRIGGER
|
||||
AS
|
||||
$$
|
||||
DECLARE killerSessionId Int;
|
||||
DECLARE killerId Int;
|
||||
DECLARE weaponId Int;
|
||||
BEGIN
|
||||
killerId := NEW.killer_id;
|
||||
weaponId := NEW.weapon_id;
|
||||
CALL proc_sessionnumber_get(killerId, killerSessionId);
|
||||
BEGIN
|
||||
UPDATE weaponstatsession
|
||||
SET assists = assists + 1
|
||||
WHERE avatar_id = killerId AND session_id = killerSessionId AND weapon_id = weaponId;
|
||||
END;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE TRIGGER psf_assistactivity_updateRelatedStats
|
||||
AFTER INSERT
|
||||
ON killactivity
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION fn_assistactivity_updateRelatedStats();
|
||||
|
|
@ -231,17 +231,17 @@ object VehicleSpawnPadControlTest {
|
|||
override def SetupNumberPools(): Unit = {}
|
||||
}
|
||||
zone.GUID(guid)
|
||||
zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
|
||||
zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.currentTimeMillis()}")
|
||||
|
||||
// Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions
|
||||
// with actor probe setting
|
||||
// TODO(chord): Remove when Zone supports notification of booting being complete
|
||||
Thread.sleep(5000)
|
||||
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), s"vehicle-control-${System.nanoTime()}")
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), s"vehicle-control-${System.currentTimeMillis()}")
|
||||
|
||||
val pad = VehicleSpawnPad(GlobalDefinitions.mb_pad_creation)
|
||||
pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.nanoTime()}")
|
||||
pad.Actor = system.actorOf(Props(classOf[VehicleSpawnControl], pad), s"test-pad-${System.currentTimeMillis()}")
|
||||
pad.Owner =
|
||||
new Building("Building", building_guid = 0, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
|
||||
pad.Owner.Faction = faction
|
||||
|
|
|
|||
|
|
@ -75,15 +75,6 @@ game {
|
|||
third-party = no
|
||||
}
|
||||
|
||||
# Battle experience rate
|
||||
bep-rate = 1.0
|
||||
|
||||
# Command experience rate
|
||||
cep-rate = 1.0
|
||||
|
||||
# The maximum command experience that can be earned in a facility capture based on squad size
|
||||
maximum-cep-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860]
|
||||
|
||||
# Modify the amount of mending per autorepair tick for facility amenities
|
||||
amenity-autorepair-rate = 1.0
|
||||
|
||||
|
|
@ -226,6 +217,163 @@ game {
|
|||
|
||||
# Don't ask.
|
||||
doors-can-be-opened-by-med-app-from-this-distance = 5.05
|
||||
|
||||
# How the experience calculates
|
||||
experience {
|
||||
# The short contribution time when events are collected and evaluated.
|
||||
short-contribution-time = 300000
|
||||
# The long contribution time when events are collected and evaluated
|
||||
# even factoring the same events from the short contribution time.
|
||||
# As a result, when comparing the two event lists, similar actors may appear
|
||||
# but their contributions may be different.
|
||||
long-contribution-time = 600000
|
||||
|
||||
# Battle experience points
|
||||
# BEP is to be calculated in relation to how valuable a kill is worth.
|
||||
bep = {
|
||||
# After all calculations are complete, multiple the result by this value
|
||||
rate = 1.0
|
||||
# These numbers are to determine the starting value for a particular kill
|
||||
base = {
|
||||
# Black Ops multiplies the base value by this much
|
||||
bops-multiplier = 10.0
|
||||
# If the player who died ever utilized a mechanized assault exo-suit
|
||||
as-max = 250
|
||||
# The player who died got at least one kill
|
||||
with-kills = 100
|
||||
# The player who died was mounted in a vehicle at the time of death
|
||||
as-mounted = 100
|
||||
# The player who died after having been in the game world for a while after spawning.
|
||||
# Dying before this is often called a "spawn kill".
|
||||
mature = 50
|
||||
}
|
||||
}
|
||||
|
||||
# Support experience points
|
||||
# The events from which support experience rises are numerous.
|
||||
# Calculation is determined by the selection of an "event" that decides how the values are combined.
|
||||
sep = {
|
||||
# After all calculations are complete, multiple the result by this value
|
||||
rate = 1.0
|
||||
# When using an advanced nanite transport to deposit into the resource silo of a major facility,
|
||||
# for reaching the maximum amount of a single deposit,
|
||||
# reward the user with this amount of support experience points.
|
||||
# Small deposits reward only a percentage of this value.
|
||||
ntu-silo-deposit-reward = 100
|
||||
# When the event can not be found, this flat sum is rewarded.
|
||||
# This should not be treated as a feature.
|
||||
# It is a bug.
|
||||
# Check your event label calls.
|
||||
can-not-find-event-default-value = 15
|
||||
# The events by which support experience calculation occurs.
|
||||
# Events can be composed of three parts: a base value, a per-use (shots) value, and an active amount value.
|
||||
# "Per-use" relies on knowledge from the server about the number of times this exact action occurred before the event.
|
||||
# "Active amount" relies on knowledge from the server about how much of the changes for this event are still valid.
|
||||
# Some changes can be undone by other events or other behavior.
|
||||
#
|
||||
# name - label by which this event is organized
|
||||
# base - whole number value
|
||||
# shots-multiplier - whether use count matters for this event
|
||||
# - when set to 0.0 (default), it does not
|
||||
# shots-limit - upper limit of use count
|
||||
# - cap the count here, if higher
|
||||
# shots-cutoff - if the use count exceeds this number, the event no longer applies
|
||||
# - a hard limit that should zero the contribution reward
|
||||
# - the *-cutoff should probably apply before *-limit, maybe
|
||||
# shots-nat-log - when set, may the use count to a natural logarithmic curve
|
||||
# - actually the exponent on the use count before the logarithm
|
||||
# - similar to shots-limit, but the curve plateaus quickly
|
||||
# amount-multiplier - whether active amount matters for this event
|
||||
# - when set to 0.0 (default), it does not
|
||||
events = [
|
||||
{
|
||||
name = "support-heal"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
amount-multiplier = 2.0
|
||||
}
|
||||
{
|
||||
name = "support-repair"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
}
|
||||
{
|
||||
name = "support-repair-terminal"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
}
|
||||
{
|
||||
name = "support-repair-turret"
|
||||
base = 10
|
||||
shots-multiplier = 5.0
|
||||
shots-limit = 100
|
||||
}
|
||||
{
|
||||
name = "mounted-kill"
|
||||
base = 25
|
||||
}
|
||||
{
|
||||
name = "router"
|
||||
base = 15
|
||||
}
|
||||
{
|
||||
name = "hotdrop"
|
||||
base = 25
|
||||
}
|
||||
{
|
||||
name = "hack"
|
||||
base = 5
|
||||
amount-multiplier = 5.0
|
||||
}
|
||||
{
|
||||
name = "ams-resupply"
|
||||
base = 15
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
}
|
||||
{
|
||||
name = "lodestar-repair"
|
||||
base = 10
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
shots-limit = 100
|
||||
amount-multiplier = 1.0
|
||||
}
|
||||
{
|
||||
name = "lodestar-rearm"
|
||||
base = 10
|
||||
shots-multiplier = 1.0
|
||||
shots-nat-log = 5.0
|
||||
}
|
||||
{
|
||||
name = "revival"
|
||||
base = 0
|
||||
shots-multiplier = 15.0
|
||||
shots-nat-log = 5.0
|
||||
shots-cutoff = 10
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Support experience points
|
||||
cep = {
|
||||
# After all calculations are complete, multiple the result by this value
|
||||
rate = 1.0
|
||||
# When command experience points are rewarded to the lattice link unit carrier,
|
||||
# modify the original value by this modifier.
|
||||
llu-carrier-modifier = 0.5
|
||||
# The maximum command experience that can be earned in a facility capture based on squad size
|
||||
maximum-per-squad-size = [990, 1980, 3466, 4950, 6436, 7920, 9406, 10890, 12376, 13860]
|
||||
# When the cep has to be capped for squad size, add a small value to the capped value
|
||||
# -1 reuses the cep before being capped
|
||||
squad-size-limit-overflow = -1
|
||||
# When the cep has to be capped for squad size, calculate a small amount to add to the capped value
|
||||
squad-size-limit-overflow-multiplier = 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anti-cheat {
|
||||
|
|
|
|||
|
|
@ -851,12 +851,18 @@ object AvatarActor {
|
|||
)
|
||||
}
|
||||
|
||||
def updateToolDischargeFor(avatarId: Long, lives: Seq[Life]): Unit = {
|
||||
lives
|
||||
.flatMap { _.equipmentStats }
|
||||
.foreach { stat =>
|
||||
zones.exp.ToDatabase.reportToolDischarge(avatarId, stat)
|
||||
}
|
||||
def updateToolDischargeFor(avatar: Avatar): Unit = {
|
||||
updateToolDischargeFor(avatar.id, avatar.scorecard.CurrentLife)
|
||||
}
|
||||
|
||||
def updateToolDischargeFor(avatarId: Long, life: Life): Unit = {
|
||||
updateToolDischargeFor(avatarId, life.equipmentStats)
|
||||
}
|
||||
|
||||
def updateToolDischargeFor(avatarId: Long, stats: Seq[EquipmentStat]): Unit = {
|
||||
stats.foreach { stat =>
|
||||
zones.exp.ToDatabase.reportToolDischarge(avatarId, stat)
|
||||
}
|
||||
}
|
||||
|
||||
def toAvatar(avatar: persistence.Avatar): Avatar = {
|
||||
|
|
@ -1091,7 +1097,6 @@ class AvatarActor(
|
|||
.receiveSignal {
|
||||
case (_, PostStop) =>
|
||||
AvatarActor.avatarNoLongerLoggedIn(account.id)
|
||||
AvatarActor.updateToolDischargeFor(avatar.id.toLong, avatar.scorecard.CurrentLife +: avatar.scorecard.Lives)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
|
@ -1863,6 +1868,7 @@ class AvatarActor(
|
|||
AvatarActor.setBepOnly(avatar.id, avatar.bep + supportExperiencePool)
|
||||
}
|
||||
saveLockerFunc()
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
Behaviors.same
|
||||
}
|
||||
}
|
||||
|
|
@ -3084,6 +3090,7 @@ class AvatarActor(
|
|||
}
|
||||
|
||||
def updateDeaths(deathStat: Death): Unit = {
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
avatar.scorecard.rate(deathStat)
|
||||
val _session = session.get
|
||||
val zone = _session.zone
|
||||
|
|
@ -3138,13 +3145,13 @@ class AvatarActor(
|
|||
def updateExperienceAndType(exp: Long): (Long, ExperienceType) = {
|
||||
val _session = session.get
|
||||
val player = _session.player
|
||||
val gameOpts = Config.app.game
|
||||
val gameOpts = Config.app.game.experience.bep
|
||||
val (modifier, msg) = if (player.Carrying.contains(SpecialCarry.RabbitBall)) {
|
||||
(1.25f, ExperienceType.RabbitBall)
|
||||
} else {
|
||||
(1f, ExperienceType.Normal)
|
||||
}
|
||||
((exp * modifier * gameOpts.bepRate).toLong, msg)
|
||||
((exp * modifier * gameOpts.rate).toLong, msg)
|
||||
}
|
||||
|
||||
def updateToolDischarge(stats: EquipmentStat): Unit = {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import akka.actor.typed.scaladsl.adapter._
|
|||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.objects.zones.exp
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -225,8 +226,6 @@ class SessionAvatarHandlers(
|
|||
case AvatarResponse.DestroyDisplay(killer, victim, method, unk)
|
||||
if killer.CharId == avatar.id && killer.Faction != victim.Faction =>
|
||||
sendResponse(sessionData.destroyDisplayMessage(killer, victim, method, unk))
|
||||
//TODO Temporary thing that should go somewhere else and use proper xp values
|
||||
// avatarActor ! AvatarActor.AwardCep((100 * Config.app.game.cepRate).toLong)
|
||||
|
||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||
// guid = victim // killer = killer
|
||||
|
|
@ -398,71 +397,68 @@ class SessionAvatarHandlers(
|
|||
case AvatarResponse.UpdateKillsDeathsAssists(_, kda) =>
|
||||
avatarActor ! AvatarActor.UpdateKillsDeathsAssists(kda)
|
||||
|
||||
case AvatarResponse.AwardBep(_, bep, expType) =>
|
||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||
case AvatarResponse.AwardBep(charId, bep, expType) =>
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardBep(bep, expType)
|
||||
}
|
||||
|
||||
case AvatarResponse.AwardCep(0, cep) =>
|
||||
case AvatarResponse.AwardCep(charId, cep) =>
|
||||
//if the target player, always award (some) CEP
|
||||
if (charId == player.CharId) {
|
||||
avatarActor ! AvatarActor.AwardCep(cep)
|
||||
}
|
||||
|
||||
case AvatarResponse.FacilityCaptureRewards(buildingId, zoneNumber, cep) =>
|
||||
//must be in a squad to earn experience
|
||||
val id = player.CharId
|
||||
val cepConfig = Config.app.game.experience.cep
|
||||
val charId = player.CharId
|
||||
val squadUI = sessionData.squad.squadUI
|
||||
val participation = continent
|
||||
.Buildings
|
||||
.values
|
||||
.collect { case building if {
|
||||
val soi = building.Definition.SOIRadius * building.Definition.SOIRadius
|
||||
val pos = player.Position.xy
|
||||
Vector3.DistanceSquared(building.Position.xy, pos) < soi
|
||||
} =>
|
||||
.Building(buildingId)
|
||||
.map { building =>
|
||||
building.Participation.PlayerContribution()
|
||||
}
|
||||
squadUI
|
||||
.find { _._1 == id }
|
||||
.find { _._1 == charId }
|
||||
.collect {
|
||||
case (_, elem) if elem.index == 0 =>
|
||||
//squad leader earns CEP, modified by squad effort
|
||||
val maxRate: Long = {
|
||||
val maxCepList = Config.app.game.maximumCepPerSquadSize
|
||||
val squadSize: Int = {
|
||||
val squadSizeList: Iterable[Int] = participation
|
||||
.map { facilityMap =>
|
||||
squadUI.count { case (id, _) => facilityMap.contains(id) }
|
||||
}
|
||||
if (squadSizeList.nonEmpty) {
|
||||
squadSizeList.max
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
//squad leader earns CEP, modified by squad effort, capped by squad size present during the capture
|
||||
val squadParticipation = participation match {
|
||||
case Some(map) => map.filter { case (id, _) => squadUI.contains(id) }
|
||||
case _ => Map.empty[Long, Float]
|
||||
}
|
||||
val maxCepBySquadSize: Long = {
|
||||
val maxCepList = cepConfig.maximumPerSquadSize
|
||||
val squadSize: Int = squadParticipation.size
|
||||
maxCepList.lift(squadSize - 1).getOrElse(squadSize * maxCepList.head).toLong
|
||||
}
|
||||
val groupContribution: Float = {
|
||||
val eachSquadMemberParticipation: Iterable[Float] = participation.map { facilityMap =>
|
||||
val foundSquadMemberParticipation: Iterable[Float] = squadUI
|
||||
.keys
|
||||
.flatMap { facilityMap.get }
|
||||
if (foundSquadMemberParticipation.nonEmpty) {
|
||||
foundSquadMemberParticipation.sum / 10f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
if (eachSquadMemberParticipation.nonEmpty) {
|
||||
eachSquadMemberParticipation.max
|
||||
val groupContribution: Float = squadUI
|
||||
.map { case (id, _) => (id, squadParticipation.getOrElse(id, 0f) / 10f) }
|
||||
.values
|
||||
.max
|
||||
val modifiedExp: Long = (cep.toFloat * groupContribution).toLong
|
||||
val cappedModifiedExp: Long = math.min(modifiedExp, maxCepBySquadSize)
|
||||
val finalExp: Long = if (modifiedExp > cappedModifiedExp) {
|
||||
val overLimitOverflow = if (cepConfig.squadSizeLimitOverflow == -1) {
|
||||
cep.toFloat
|
||||
} else {
|
||||
0
|
||||
cepConfig.squadSizeLimitOverflow.toFloat
|
||||
}
|
||||
cappedModifiedExp + (overLimitOverflow * cepConfig.squadSizeLimitOverflowMultiplier).toLong
|
||||
} else {
|
||||
cappedModifiedExp
|
||||
}
|
||||
val modifiedExp: Long = math.min((cep.toFloat * groupContribution).toLong, maxRate)
|
||||
avatarActor ! AvatarActor.AwardCep(modifiedExp)
|
||||
Some(modifiedExp)
|
||||
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, finalExp, expType="cep")
|
||||
avatarActor ! AvatarActor.AwardCep(finalExp)
|
||||
Some(finalExp)
|
||||
|
||||
case _ =>
|
||||
//squad member earns BEP based on CEP, modified by personal effort
|
||||
val individualContribution = {
|
||||
val contributionList = for {
|
||||
facilityMap <- participation
|
||||
if facilityMap.contains(id)
|
||||
} yield facilityMap(id)
|
||||
if facilityMap.contains(charId)
|
||||
} yield facilityMap(charId)
|
||||
if (contributionList.nonEmpty) {
|
||||
contributionList.max
|
||||
} else {
|
||||
|
|
@ -470,26 +466,11 @@ class SessionAvatarHandlers(
|
|||
}
|
||||
}
|
||||
val modifiedExp = (cep * individualContribution).toLong
|
||||
exp.ToDatabase.reportFacilityCapture(charId, buildingId, zoneNumber, modifiedExp, expType="bep")
|
||||
avatarActor ! AvatarActor.AwardBep(modifiedExp, ExperienceType.Normal)
|
||||
Some(modifiedExp)
|
||||
}
|
||||
|
||||
case AvatarResponse.AwardCep(charId, cep) =>
|
||||
//if the target player, always award (some) CEP
|
||||
val squadUI = sessionData.squad.squadUI
|
||||
if (charId == player.CharId) {
|
||||
val maxRate: Long = squadUI.find { _._1 == avatar.id } match {
|
||||
case Some((_, elem)) if elem.index == 0 =>
|
||||
val thisZone = continent.Number
|
||||
val squadSize = squadUI.count { case (_, e) => e.zone == thisZone } - 1
|
||||
val maxCepList = Config.app.game.maximumCepPerSquadSize
|
||||
maxCepList.lift(squadSize).getOrElse(squadSize * maxCepList.head).toLong
|
||||
case _ =>
|
||||
Config.app.game.maximumCepPerSquadSize.head.toLong
|
||||
}
|
||||
avatarActor ! AvatarActor.AwardCep(math.min(cep, maxRate))
|
||||
}
|
||||
|
||||
case AvatarResponse.SendResponse(msg) =>
|
||||
sendResponse(msg)
|
||||
|
||||
|
|
@ -526,6 +507,7 @@ class SessionAvatarHandlers(
|
|||
sessionData.renewCharSavedTimer(fixedLen = 1800L, varLen = 0L)
|
||||
|
||||
//player state changes
|
||||
AvatarActor.updateToolDischargeFor(avatar)
|
||||
player.FreeHand.Equipment.foreach { item =>
|
||||
DropEquipmentFromInventory(player)(item)
|
||||
}
|
||||
|
|
@ -540,9 +522,7 @@ class SessionAvatarHandlers(
|
|||
}
|
||||
sessionData.playerActionsToCancel()
|
||||
sessionData.terminals.CancelAllProximityUnits()
|
||||
sessionData.zoning
|
||||
AvatarActor.savePlayerLocation(player)
|
||||
sessionData.shooting.reportOngoingShots(sessionData.shooting.reportOngoingShotsToDatabase)
|
||||
sessionData.zoning.spawn.shiftPosition = Some(player.Position)
|
||||
|
||||
//respawn
|
||||
|
|
|
|||
|
|
@ -2458,9 +2458,9 @@ class SessionData(
|
|||
src: PlanetSideGameObject with TelepadLike,
|
||||
dest: PlanetSideGameObject with TelepadLike
|
||||
): Unit = {
|
||||
val time = System.nanoTime
|
||||
val time = System.currentTimeMillis()
|
||||
if (
|
||||
time - recentTeleportAttempt > (2 seconds).toNanos && router.DeploymentState == DriveState.Deployed &&
|
||||
time - recentTeleportAttempt > 2000L && router.DeploymentState == DriveState.Deployed &&
|
||||
internalTelepad.Active &&
|
||||
remoteTelepad.Active
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import akka.actor.{ActorContext, ActorRef, Cancellable, typed}
|
|||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import net.psforever.login.WorldSession
|
||||
import net.psforever.objects.avatar.scoring.ScoreCard
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.mount.Seat
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
|
|
@ -2342,6 +2343,7 @@ class ZoningOperations(
|
|||
player.avatar = player.avatar.copy(stamina = avatar.maxStamina)
|
||||
avatarActor ! AvatarActor.RestoreStamina(avatar.maxStamina)
|
||||
avatarActor ! AvatarActor.ResetImplants()
|
||||
zones.exp.ToDatabase.reportRespawns(tplayer.CharId, ScoreCard.reviveCount(player.avatar.scorecard.CurrentLife))
|
||||
val obj = Player.Respawn(tplayer)
|
||||
DefinitionUtil.applyDefaultLoadout(obj)
|
||||
obj.death_by = tplayer.death_by
|
||||
|
|
|
|||
|
|
@ -9847,6 +9847,7 @@ object GlobalDefinitions {
|
|||
resource_silo.Damageable = false
|
||||
resource_silo.Repairable = false
|
||||
resource_silo.MaxNtuCapacitor = 1000
|
||||
resource_silo.ChargeTime = 105.seconds //from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402)
|
||||
|
||||
capture_terminal.Name = "capture_terminal"
|
||||
capture_terminal.Damageable = false
|
||||
|
|
@ -9856,7 +9857,7 @@ object GlobalDefinitions {
|
|||
secondary_capture.Name = "secondary_capture"
|
||||
secondary_capture.Damageable = false
|
||||
secondary_capture.Repairable = false
|
||||
secondary_capture.FacilityHackTime = 1.nanosecond
|
||||
secondary_capture.FacilityHackTime = 1.millisecond
|
||||
|
||||
vanu_control_console.Name = "vanu_control_console"
|
||||
vanu_control_console.Damageable = false
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ class Player(var avatar: Avatar)
|
|||
Health = Definition.DefaultHealth
|
||||
Armor = MaxArmor
|
||||
Capacitor = 0
|
||||
avatar.scorecard.respawn()
|
||||
released = false
|
||||
}
|
||||
isAlive
|
||||
|
|
@ -124,13 +125,16 @@ class Player(var avatar: Avatar)
|
|||
def Revive: Boolean = {
|
||||
Destroyed = false
|
||||
Health = Definition.DefaultHealth
|
||||
avatar.scorecard.revive()
|
||||
released = false
|
||||
true
|
||||
}
|
||||
|
||||
def Release: Boolean = {
|
||||
released = true
|
||||
backpack = !isAlive
|
||||
if (!released) {
|
||||
released = true
|
||||
backpack = !isAlive
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
|
|
@ -626,6 +630,7 @@ object Player {
|
|||
if (player.Release) {
|
||||
val obj = new Player(player.avatar)
|
||||
obj.Continent = player.Continent
|
||||
obj.avatar.scorecard.respawn()
|
||||
obj
|
||||
} else {
|
||||
player
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class Tool(private val toolDef: ToolDefinition)
|
|||
}
|
||||
|
||||
def Discharge(rounds: Option[Int] = None): Int = {
|
||||
lastDischarge = System.nanoTime()
|
||||
lastDischarge = System.currentTimeMillis()
|
||||
Magazine = FireMode.Discharge(this, rounds)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ final case class Life(
|
|||
assists: Seq[Assist],
|
||||
death: Option[Death],
|
||||
equipmentStats: Seq[EquipmentStat],
|
||||
supportExperience: Long
|
||||
supportExperience: Long,
|
||||
prior: Option[Life]
|
||||
)
|
||||
|
||||
object Life {
|
||||
def apply(): Life = Life(Nil, Nil, None, Nil, 0)
|
||||
def apply(): Life = Life(Nil, Nil, None, Nil, 0, None)
|
||||
|
||||
def revive(prior: Life): Life = Life(Nil, Nil, None, Nil, 0, Some(prior))
|
||||
|
||||
def bep(life: Life): Long = {
|
||||
life.kills.foldLeft(0L)(_ + _.experienceEarned) + life.assists.foldLeft(0L)(_ + _.experienceEarned)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class ScoreCard() {
|
|||
|
||||
def Lives: Seq[Life] = lives
|
||||
|
||||
def AllLives: Seq[Life] = curr +: lives
|
||||
|
||||
def Kills: Seq[Kill] = lives.flatMap { _.kills } ++ curr.kills
|
||||
|
||||
def KillStatistics: Map[Int, Statistic] = killStatistics.toMap
|
||||
|
|
@ -39,17 +41,41 @@ class ScoreCard() {
|
|||
ScoreCard.updateStatisticsFor(assistStatistics, wid.equipment, faction)
|
||||
}
|
||||
case d: Death =>
|
||||
val expired = curr
|
||||
curr = Life()
|
||||
lives = expired.copy(death = Some(d)) +: lives
|
||||
curr = curr.copy(death = Some(d))
|
||||
case value: Long =>
|
||||
curr = curr.copy(supportExperience = curr.supportExperience + value)
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
def revive(): Unit = {
|
||||
curr = Life.revive(curr)
|
||||
}
|
||||
|
||||
def respawn(): Unit = {
|
||||
val death = curr
|
||||
curr = Life()
|
||||
lives = death +: lives
|
||||
}
|
||||
}
|
||||
|
||||
object ScoreCard {
|
||||
def respawnCount(card: ScoreCard): Int = {
|
||||
card.AllLives.foldLeft(0)(_ + recursiveReviveCount(_, count = 0))
|
||||
}
|
||||
|
||||
def reviveCount(life: Life): Int = {
|
||||
recursiveReviveCount(life, count = 0)
|
||||
}
|
||||
|
||||
@tailrec
|
||||
private def recursiveReviveCount(life: Life, count: Int): Int = {
|
||||
life.prior match {
|
||||
case None => count
|
||||
case Some(previousLife) => recursiveReviveCount(previousLife, count + 1)
|
||||
}
|
||||
}
|
||||
|
||||
private def updateEquipmentStat(curr: Life, entry: EquipmentStat): Life = {
|
||||
updateEquipmentStat(curr, entry, entry.objectId, entry.kills, entry.assists)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class CaptureFlagConverter extends ObjectCreateConverter[CaptureFlag]() {
|
|||
case _ => Hackable.HackInfo(PlayerSource("", PlanetSideEmpire.NEUTRAL, Vector3.Zero), PlanetSideGUID(0), 0L, 0L)
|
||||
}
|
||||
|
||||
val millisecondsRemaining = TimeUnit.MILLISECONDS.convert(math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.nanoTime), TimeUnit.NANOSECONDS)
|
||||
val millisecondsRemaining = math.max(0, hackInfo.hackStartTime + hackInfo.hackDuration - System.currentTimeMillis())
|
||||
|
||||
Success(
|
||||
CaptureFlagData(
|
||||
|
|
|
|||
|
|
@ -25,14 +25,14 @@ trait Hackable {
|
|||
def HackedBy_=(agent: Option[Player]): Option[HackInfo] = {
|
||||
(hackedBy, agent) match {
|
||||
case (None, Some(actor)) =>
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L))
|
||||
case (Some(info), Some(actor)) =>
|
||||
if (actor.Faction == this.Faction) {
|
||||
//hack cleared
|
||||
hackedBy = None
|
||||
} else if (actor.Faction != info.hackerFaction) {
|
||||
//override the hack state with a new hack state if the new user has different faction affiliation
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.nanoTime, 0L))
|
||||
hackedBy = Some(HackInfo(PlayerSource(actor), actor.GUID, System.currentTimeMillis(), 0L))
|
||||
}
|
||||
case (_, None) =>
|
||||
hackedBy = None
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ import net.psforever.actors.zone.BuildingActor
|
|||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.transfer.TransferBehavior
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.zones
|
||||
import net.psforever.objects.{GlobalDefinitions, Ntu, NtuContainer, NtuStorageBehavior, Vehicle}
|
||||
import net.psforever.types.{ExperienceType, PlanetSideEmpire}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -188,11 +190,16 @@ class ResourceSiloControl(resourceSilo: ResourceSilo)
|
|||
.map { v => (v, v.Owners) }
|
||||
.collect { case (vehicle, Some(owner)) =>
|
||||
//experience is reported as normal
|
||||
val deposit: Long = 100L * math.floor(amount).toLong / math.floor(resourceSilo.MaxNtuCapacitor / 105f).toLong
|
||||
val deposit: Long =
|
||||
(Config.app.game.experience.sep.ntuSiloDepositReward.toFloat *
|
||||
math.floor(amount).toFloat /
|
||||
math.floor(resourceSilo.MaxNtuCapacitor / resourceSilo.Definition.ChargeTime.toMillis.toFloat)
|
||||
).toLong
|
||||
vehicle.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
owner.name,
|
||||
AvatarAction.AwardBep(0, deposit, ExperienceType.Normal)
|
||||
)
|
||||
zones.exp.ToDatabase.reportNtuActivity(owner.charId, resourceSilo.Zone.Number, resourceSilo.Owner.GUID.guid, deposit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,16 @@ package net.psforever.objects.serverobject.resourcesilo
|
|||
import net.psforever.objects.NtuContainerDefinition
|
||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* The definition for any `Resource Silo`.
|
||||
* Object Id 731.
|
||||
*/
|
||||
class ResourceSiloDefinition extends AmenityDefinition(731)
|
||||
with NtuContainerDefinition {
|
||||
var ChargeTime: FiniteDuration = 0.seconds
|
||||
|
||||
Name = "resource_silo"
|
||||
MaxNtuCapacitor = 1000
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,8 +183,7 @@ class Building(
|
|||
case Some(obj: CaptureTerminal with Hackable) =>
|
||||
obj.HackedBy match {
|
||||
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
||||
val hack_time_remaining_ms =
|
||||
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
|
||||
val hack_time_remaining_ms = math.max(0, start + length - System.currentTimeMillis())
|
||||
(true, p.Faction, hack_time_remaining_ms)
|
||||
case _ =>
|
||||
(false, PlanetSideEmpire.NEUTRAL, 0L)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ import scala.collection.mutable
|
|||
trait FacilityHackParticipation extends ParticipationLogic {
|
||||
protected var lastInfoRequest: Long = 0L
|
||||
protected var infoRequestOverTime: Seq[Long] = Seq[Long]()
|
||||
/*
|
||||
key: unique player identifier
|
||||
/**
|
||||
key: unique player identifier<br>
|
||||
values: last player entry, number of times updated, time of last update (POSIX time)
|
||||
*/
|
||||
protected var playerContribution: mutable.LongMap[(Player, Int, Long)] = mutable.LongMap[(Player, Int, Long)]()
|
||||
protected var playerPopulationOverTime: Seq[Map[PlanetSideEmpire.Value, Int]] = Seq[ Map[PlanetSideEmpire.Value, Int]]()
|
||||
protected var playerPopulationOverTime: Seq[Map[PlanetSideEmpire.Value, Int]] = Seq[Map[PlanetSideEmpire.Value, Int]]()
|
||||
|
||||
def PlayerContribution(timeDelay: Long): Map[Long, Float] = {
|
||||
playerContribution
|
||||
|
|
@ -49,6 +50,13 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminate participation for players who have no submitted updates within the time period.
|
||||
* @param list current list of players
|
||||
* @param now current time (ms)
|
||||
* @param before how long before the current time beyond which players should be eliminated (ms)
|
||||
* @see `timeSensitiveFilterAndAppend`
|
||||
*/
|
||||
protected def updatePopulationOverTime(list: List[Player], now: Long, before: Long): Unit = {
|
||||
var populationList = list
|
||||
val layer = PlanetSideEmpire.values.map { faction =>
|
||||
|
|
@ -56,70 +64,82 @@ trait FacilityHackParticipation extends ParticipationLogic {
|
|||
populationList = everyoneElse
|
||||
(faction, isFaction.size)
|
||||
}.toMap[PlanetSideEmpire.Value, Int]
|
||||
playerPopulationOverTime = timeSensitiveFilterAndAppend(playerPopulationOverTime, layer, now, before)
|
||||
playerPopulationOverTime = timeSensitiveFilterAndAppend(playerPopulationOverTime, layer, now - before)
|
||||
}
|
||||
|
||||
protected def updateTime(now: Long): Unit = {
|
||||
infoRequestOverTime = timeSensitiveFilterAndAppend(infoRequestOverTime, now, now - 900000L)
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminate entries from the primary input list based on time entries in a secondary time record list.
|
||||
* The time record list must be updated independently.
|
||||
* @param list input list whose entries are edited against time and then is appended
|
||||
* @param newEntry new entry of the appropriate type to append to the end of the output list
|
||||
* @param beforeTime how long before the current time beyond which entries in the input list should be eliminated (ms)
|
||||
* @tparam T it does not matter what the type is
|
||||
* @return the modified list
|
||||
*/
|
||||
protected def timeSensitiveFilterAndAppend[T](
|
||||
list: Seq[T],
|
||||
newEntry: T,
|
||||
now: Long = System.currentTimeMillis(),
|
||||
before: Long
|
||||
beforeTime: Long
|
||||
): Seq[T] = {
|
||||
infoRequestOverTime match {
|
||||
case Nil =>
|
||||
Seq(newEntry)
|
||||
case Nil => Seq(newEntry)
|
||||
case _ =>
|
||||
val beforeTime = now - before
|
||||
(infoRequestOverTime.indexWhere { _ >= beforeTime } match {
|
||||
case -1 =>
|
||||
list
|
||||
case cutOffIndex =>
|
||||
list.drop(cutOffIndex)
|
||||
case -1 => list
|
||||
case cutOffIndex => list.drop(cutOffIndex)
|
||||
}) :+ newEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object FacilityHackParticipation {
|
||||
private[participation] def calculateExperienceFromKills(
|
||||
private[participation] def allocateKillsByPlayers(
|
||||
center: Vector3,
|
||||
radius: Float,
|
||||
hackStart: Long,
|
||||
completionTime: Long,
|
||||
opposingFaction: PlanetSideEmpire.Value,
|
||||
contributionVictor: Iterable[(Player, Int, Long)],
|
||||
contributionOpposingSize: Int
|
||||
): Long = {
|
||||
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||
val killMapFunc: Iterable[(Player, Int, Long)] => Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||
killsEarnedPerPlayerDuringHack(center.xy, radius * radius, hackStart, hackStart + completionTime, opposingFaction)
|
||||
}
|
||||
val killMapValues = killMapFunc(contributionVictor)
|
||||
val totalExperienceFromKills = killMapValues.flatMap { _._3.map { _.experienceEarned } }.sum
|
||||
val experienceModifier = {
|
||||
if (contributionOpposingSize > 0 && contributionOpposingSize < 10) {
|
||||
contributionOpposingSize * 0.1f + math.random()
|
||||
} else {
|
||||
contributionOpposingSize * 0.1f
|
||||
}
|
||||
}
|
||||
(totalExperienceFromKills * experienceModifier).toLong
|
||||
killMapFunc(contributionVictor)
|
||||
}
|
||||
|
||||
private def killsEarnedPerPlayerDuringHack(
|
||||
centerXY: Vector3,
|
||||
distanceSq: Float,
|
||||
start: Long,
|
||||
end: Long,
|
||||
faction: PlanetSideEmpire.Value
|
||||
)
|
||||
(
|
||||
list: Iterable[(Player, Int, Long)]
|
||||
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||
private[participation] def calculateExperienceFromKills(
|
||||
killMapValues: Iterable[(UniquePlayer, Float, Seq[Kill])],
|
||||
contributionOpposingSize: Int
|
||||
): Long = {
|
||||
val totalExperienceFromKills = killMapValues
|
||||
.flatMap { _._3.map { _.experienceEarned } }
|
||||
.sum
|
||||
.toFloat
|
||||
(totalExperienceFromKills * contributionOpposingSize.toFloat * 0.1d).toLong
|
||||
}
|
||||
|
||||
private[participation] def killsEarnedPerPlayerDuringHack(
|
||||
centerXY: Vector3,
|
||||
distanceSq: Float,
|
||||
start: Long,
|
||||
end: Long,
|
||||
faction: PlanetSideEmpire.Value
|
||||
)
|
||||
(
|
||||
list: Iterable[(Player, Int, Long)]
|
||||
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||
val duration = end - start
|
||||
list.map { case (p, d, _) =>
|
||||
val killList = p.avatar.scorecard.Kills.filter { k =>
|
||||
val killTime = k.info.interaction.hitTime
|
||||
k.victim.Faction == faction && start < killTime && killTime < end && Vector3.DistanceSquared(centerXY, k.info.interaction.hitPos.xy) < distanceSq
|
||||
k.victim.Faction == faction &&
|
||||
start < killTime &&
|
||||
killTime <= end &&
|
||||
Vector3.DistanceSquared(centerXY, k.info.interaction.hitPos.xy) < distanceSq
|
||||
}
|
||||
(PlayerSource(p).unique, math.min(d, duration).toFloat / duration.toFloat, killList)
|
||||
}
|
||||
|
|
@ -220,7 +240,8 @@ object FacilityHackParticipation {
|
|||
victorPop <- victorPopulationNumbers
|
||||
opposePop <- opposingPopulationNumbers
|
||||
out = if (
|
||||
(opposePop < victorPop && opposePop * healthyPercentage > victorPop) ||
|
||||
(opposePop + victorPop < 8) ||
|
||||
(opposePop < victorPop && opposePop * healthyPercentage > victorPop) ||
|
||||
(opposePop > victorPop && victorPop * healthyPercentage > opposePop)
|
||||
) {
|
||||
1f //balanced enough population
|
||||
|
|
@ -241,7 +262,7 @@ object FacilityHackParticipation {
|
|||
overwhelmingOddsBonus: Long
|
||||
): Long = {
|
||||
if (opposingSize * steamrollPercentage < victorSize.toFloat) {
|
||||
-steamrollBonus * (victorSize - opposingSize) //steamroll by the victor
|
||||
0L //steamroll by the victor
|
||||
} else if (victorSize * overwhelmingOddsPercentage <= opposingSize.toFloat) {
|
||||
overwhelmingOddsBonus + opposingSize + victorSize //victory against overwhelming odds
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.objects.serverobject.structures.participation
|
||||
|
||||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.serverobject.structures.{Building, StructureType}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
|
||||
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import akka.pattern.ask
|
||||
import akka.util.Timeout
|
||||
import net.psforever.objects.avatar.scoring.Kill
|
||||
import net.psforever.objects.zones.exp.ToDatabase
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -49,17 +51,13 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
requestLayers.completeWith(request)
|
||||
request.onComplete {
|
||||
case Success(ZoneHotSpotProjector.ExposedHeat(_, _, activity)) =>
|
||||
hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis(), before = 900000L)
|
||||
hotSpotLayersOverTime = timeSensitiveFilterAndAppend(hotSpotLayersOverTime, activity, System.currentTimeMillis() - 900000L)
|
||||
case _ =>
|
||||
requestLayers.completeWith(Future(ZoneHotSpotProjector.ExposedHeat(Vector3.Zero, 0, Nil)))
|
||||
}
|
||||
requestLayers.future
|
||||
}
|
||||
|
||||
private def updateTime(now: Long): Unit = {
|
||||
infoRequestOverTime = timeSensitiveFilterAndAppend(infoRequestOverTime, now, now, before = 900000L)
|
||||
}
|
||||
|
||||
def RewardFacilityCapture(
|
||||
defenderFaction: PlanetSideEmpire.Value,
|
||||
attackingFaction: PlanetSideEmpire.Value,
|
||||
|
|
@ -70,11 +68,12 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
): Unit = {
|
||||
val curr = System.currentTimeMillis()
|
||||
val hackStart = curr - completionTime
|
||||
val (victorFaction, opposingFaction, flagCarrier) = if (!isResecured) {
|
||||
val carrier = building.GetFlagSocket.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||
(attackingFaction, defenderFaction, carrier)
|
||||
val socketOpt = building.GetFlagSocket
|
||||
val (victorFaction, opposingFaction, hasFlag, flagCarrier) = if (!isResecured) {
|
||||
val carrier = socketOpt.flatMap(_.previousFlag).flatMap(_.Carrier)
|
||||
(attackingFaction, defenderFaction, socketOpt.nonEmpty, carrier)
|
||||
} else {
|
||||
(defenderFaction, attackingFaction, None)
|
||||
(defenderFaction, attackingFaction, socketOpt.nonEmpty, None)
|
||||
}
|
||||
val (contributionVictor, contributionOpposing, _) = {
|
||||
val (a, b1) = playerContribution.partition { case (_, (p, _, _)) => p.Faction == victorFaction }
|
||||
|
|
@ -83,17 +82,6 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
}
|
||||
val contributionVictorSize = contributionVictor.size
|
||||
if (contributionVictorSize > 0) {
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
//1) experience from killing opposingFaction across duration of hack
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius - 50f,
|
||||
hackStart,
|
||||
completionTime,
|
||||
opposingFaction,
|
||||
contributionVictor,
|
||||
contributionOpposingSize
|
||||
)
|
||||
//setup for ...
|
||||
val populationIndices = playerPopulationOverTime.indices
|
||||
val allFactions = PlanetSideEmpire.values.filterNot { _ == PlanetSideEmpire.NEUTRAL }.toSeq
|
||||
|
|
@ -103,32 +91,43 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||
}
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
val killsByPlayersNotInTower = eliminateClosestTowerFromParticipating(
|
||||
building,
|
||||
FacilityHackParticipation.allocateKillsByPlayers(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
hackStart,
|
||||
completionTime,
|
||||
opposingFaction,
|
||||
contributionOpposing
|
||||
)
|
||||
)
|
||||
//1) experience from killing opposingFaction across duration of hack
|
||||
//The kills that occurred in the facility's attached field tower's sphere of influence have been eliminated from consideration.
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
killsByPlayersNotInTower,
|
||||
contributionOpposingSize
|
||||
)
|
||||
//2) peak population modifier
|
||||
//Large facility battles should be well-rewarded.
|
||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||
opposingPopulationByLayer,
|
||||
{ pop =>
|
||||
if (pop > 59) 0.5f
|
||||
else if (pop > 29) 0.4f
|
||||
else if (pop > 25) 0.3f
|
||||
else 0.25f
|
||||
if (pop > 75) 0.9f
|
||||
else if (pop > 59) 0.6f
|
||||
else if (pop > 29) 0.55f
|
||||
else if (pop > 25) 0.5f
|
||||
else 0.45f
|
||||
},
|
||||
3
|
||||
4
|
||||
)
|
||||
//3) competition bonus
|
||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||
contributionVictorSize,
|
||||
contributionOpposingSize,
|
||||
steamrollPercentage = 1.25f,
|
||||
steamrollBonus = 5L,
|
||||
overwhelmingOddsPercentage = 0.5f,
|
||||
overwhelmingOddsBonus = 100L
|
||||
)
|
||||
//4) competition multiplier
|
||||
//3) competition multiplier
|
||||
val competitionMultiplier: Float = {
|
||||
val populationBalanceModifier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||
victorPopulationByLayer,
|
||||
opposingPopulationByLayer,
|
||||
healthyPercentage = 1.25f
|
||||
healthyPercentage = 1.5f
|
||||
)
|
||||
//compensate for heat
|
||||
val regionHeatMapProgression = {
|
||||
|
|
@ -140,7 +139,11 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
((A-1, B-2, C-3), (D-1, E-2, F-3), (G-1, H-2, I-3)) ... (1->(A, D, G), 2->(B, E, H), 3->(C, F, I))
|
||||
*/
|
||||
val finalMap = mutable.HashMap[Vector3, Map[PlanetSideEmpire.Value, Seq[Long]]]()
|
||||
.addAll(hotSpotLayersOverTime.head.map { entry => (entry.DisplayLocation, Map.empty) })
|
||||
.addAll(
|
||||
hotSpotLayersOverTime.take(1).flatMap { entry =>
|
||||
entry.map { f => (f.DisplayLocation, Map.empty[PlanetSideEmpire.Value, Seq[Long]]) }
|
||||
}
|
||||
)
|
||||
//note: this pre-seeding of keys allows us to skip a getOrElse call in the foldLeft
|
||||
hotSpotLayersOverTime.foldLeft(finalMap) { (map, list) =>
|
||||
list.foreach { entry =>
|
||||
|
|
@ -153,48 +156,131 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
|
|||
}.toMap
|
||||
finalMap //explicit for no good reason
|
||||
}
|
||||
val heatVictorMap = FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values
|
||||
val heatAgainstMap = FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
||||
val heatMapModifier = FacilityHackParticipation.heatMapComparison(heatVictorMap, heatAgainstMap)
|
||||
val heatMapModifier = FacilityHackParticipation.heatMapComparison(
|
||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, victorFaction).values,
|
||||
FacilityHackParticipation.diffHeatForFactionMap(regionHeatMapProgression, opposingFaction).values
|
||||
)
|
||||
heatMapModifier * populationBalanceModifier
|
||||
}
|
||||
//5) hack time modifier
|
||||
val overallTimeMultiplier: Float = if (isResecured) {
|
||||
math.max(0.5f + (hackTime - completionTime) / hackTime, 1f)
|
||||
} else {
|
||||
1f
|
||||
//4) hack time modifier
|
||||
//Captured major facilities without a lattice link unit and resecured major facilities with a lattice link unit
|
||||
// incur the full hack time if the module is not transported to a friendly facility
|
||||
//Captured major facilities with a lattice link unit and resecure major facilities without a lattice link uit
|
||||
// will incur an abbreviated duration
|
||||
val overallTimeMultiplier: Float = {
|
||||
if (
|
||||
building.Faction == PlanetSideEmpire.NEUTRAL ||
|
||||
building.NtuLevel == 0 ||
|
||||
building.Generator.map { _.Condition }.contains(PlanetSideGeneratorState.Destroyed)
|
||||
) { //the facility ran out of nanites or power during the hack or became neutral
|
||||
0f
|
||||
} else if (hasFlag) {
|
||||
if (completionTime >= hackTime) { //hack timed out without llu delivery
|
||||
0.25f
|
||||
} else if (isResecured) {
|
||||
0.5f + (if (hackTime <= completionTime * 0.3f) {
|
||||
completionTime.toFloat / hackTime.toFloat
|
||||
} else if (hackTime >= completionTime * 0.6f) {
|
||||
(hackTime - completionTime).toFloat / hackTime.toFloat
|
||||
} else {
|
||||
0f
|
||||
})
|
||||
} else {
|
||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||
}
|
||||
} else {
|
||||
if (isResecured) {
|
||||
0.5f + (hackTime - completionTime).toFloat / (2f * hackTime)
|
||||
} else {
|
||||
0.5f
|
||||
}
|
||||
}
|
||||
}
|
||||
//calculate overall command experience points and the individual player multiplier
|
||||
//5. individual contribution factors - by time
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.45f)
|
||||
case (a, (_, d, _)) if d >= 600000 =>
|
||||
(a, 0.25f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.25f * (0.5f + (d.toFloat / 600000f)))
|
||||
case (a, (_, _, _)) =>
|
||||
(a, 0.15f)
|
||||
}
|
||||
//6. competition bonus
|
||||
//This value will probably suck, and that's fine.
|
||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||
contributionVictorSize,
|
||||
contributionOpposingSize,
|
||||
steamrollPercentage = 1.25f,
|
||||
steamrollBonus = 5L,
|
||||
overwhelmingOddsPercentage = 0.5f,
|
||||
overwhelmingOddsBonus = 15L
|
||||
)
|
||||
//7. calculate overall command experience points
|
||||
val finalCep: Long = math.ceil(
|
||||
math.max(1L, baseExperienceFromFacilityCapture + competitionBonus) *
|
||||
math.max(0L, baseExperienceFromFacilityCapture) *
|
||||
populationModifier *
|
||||
competitionMultiplier *
|
||||
overallTimeMultiplier *
|
||||
Config.app.game.cepRate
|
||||
Config.app.game.experience.cep.rate + competitionBonus
|
||||
).toLong
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 600000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 1f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, d.toFloat / 6000000f)
|
||||
case (a, (_, d, t)) =>
|
||||
(a, math.max(0, d - completionTime + t).toFloat / 6000000f)
|
||||
}
|
||||
//reward participant(s)
|
||||
// classically, only players in the SOI are rewarded
|
||||
//8. reward participants
|
||||
//Classically, only players in the SOI are rewarded, and the llu runner too
|
||||
val hackerId = hacker.CharId
|
||||
val events = building.Zone.AvatarEvents
|
||||
building.PlayersInSOI
|
||||
.filter { player =>
|
||||
player.Faction == victorFaction && player.CharId != hacker.CharId && !flagCarrier.contains(player)
|
||||
}
|
||||
val playersInSoi = building.PlayersInSOI
|
||||
if (playersInSoi.exists(_.CharId == hackerId) && flagCarrier.map(_.CharId).getOrElse(0L) != hackerId) {
|
||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hackerId, finalCep))
|
||||
}
|
||||
playersInSoi
|
||||
.filter { player => player.Faction == victorFaction && player.CharId != hackerId }
|
||||
.foreach { player =>
|
||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(player.CharId, 1f)
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, (finalCep * contributionMultiplier).toLong))
|
||||
val charId = player.CharId
|
||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(charId, 1f)
|
||||
val outputValue = (finalCep * contributionMultiplier).toLong
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, outputValue))
|
||||
}
|
||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
||||
flagCarrier.collect {
|
||||
player => events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(player.CharId, (finalCep * 0.5f).toLong))
|
||||
case player if !isResecured =>
|
||||
val charId: Long = player.CharId
|
||||
val finalModifiedCep: Long = math.min(
|
||||
(hackTime - completionTime) / 1000L,
|
||||
(finalCep * Config.app.game.experience.cep.lluCarrierModifier).toLong
|
||||
)
|
||||
ToDatabase.reportFacilityCapture(
|
||||
charId,
|
||||
building.Zone.Number,
|
||||
building.GUID.guid,
|
||||
finalModifiedCep,
|
||||
expType="llu"
|
||||
)
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(charId, finalModifiedCep))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def eliminateClosestTowerFromParticipating(
|
||||
building: Building,
|
||||
list: Iterable[(UniquePlayer, Float, Seq[Kill])]
|
||||
): Iterable[(UniquePlayer, Float, Seq[Kill])] = {
|
||||
val buildingPosition = building.Position.xy
|
||||
building
|
||||
.Zone
|
||||
.Buildings
|
||||
.values
|
||||
.filter { building => building.BuildingType == StructureType.Tower }
|
||||
.minByOption { tower => Vector3.DistanceSquared(buildingPosition, tower.Position.xy) }
|
||||
.map { tower =>
|
||||
val towerPosition = tower.Position.xy
|
||||
val towerRadius = math.pow(tower.Definition.SOIRadius.toDouble * 0.7d, 2d).toFloat
|
||||
list
|
||||
.map { case (p, f, kills) =>
|
||||
val filteredKills = kills.filter { kill => Vector3.DistanceSquared(kill.victim.Position.xy, towerPosition) <= towerRadius }
|
||||
(p, f, filteredKills)
|
||||
}
|
||||
.filter { case (_, _, kills) => kills.nonEmpty }
|
||||
}
|
||||
.getOrElse(list)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.structures.participation
|
|||
import net.psforever.objects.serverobject.structures.Building
|
||||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
final case class TowerHackParticipation(building: Building) extends FacilityHackParticipation {
|
||||
|
|
@ -25,8 +25,6 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
completionTime: Long,
|
||||
isResecured: Boolean
|
||||
): Unit = {
|
||||
val curr = System.currentTimeMillis()
|
||||
val hackStart = curr - completionTime
|
||||
val (victorFaction, opposingFaction) = if (!isResecured) {
|
||||
(attackingFaction, defenderFaction)
|
||||
} else {
|
||||
|
|
@ -40,18 +38,10 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
}
|
||||
val contributionVictorSize = contributionVictor.size
|
||||
if (contributionVictorSize > 0) {
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
//1) experience from killing opposingFaction across duration of hack
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
hackStart,
|
||||
completionTime,
|
||||
opposingFaction,
|
||||
contributionVictor,
|
||||
contributionOpposingSize
|
||||
)
|
||||
//setup for ...
|
||||
import scala.concurrent.duration._
|
||||
val curr = System.currentTimeMillis()
|
||||
val contributionOpposingSize = contributionOpposing.size
|
||||
val populationIndices = playerPopulationOverTime.indices
|
||||
val allFactions = PlanetSideEmpire.values.filterNot {
|
||||
_ == PlanetSideEmpire.NEUTRAL
|
||||
|
|
@ -62,18 +52,74 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
}.toMap[PlanetSideEmpire.Value, Seq[Int]]
|
||||
(individualPopulationByLayer(victorFaction), individualPopulationByLayer(opposingFaction))
|
||||
}
|
||||
val soiPlayers = building.PlayersInSOI
|
||||
//1) experience from killing opposingFaction
|
||||
//Because the hack duration of towers is instantaneous, the prior period of five minutes is artificially selected.
|
||||
val baseExperienceFromFacilityCapture: Long = FacilityHackParticipation.calculateExperienceFromKills(
|
||||
FacilityHackParticipation.allocateKillsByPlayers(
|
||||
building.Position,
|
||||
building.Definition.SOIRadius.toFloat,
|
||||
curr - 5.minutes.toMillis,
|
||||
curr,
|
||||
opposingFaction,
|
||||
contributionVictor
|
||||
),
|
||||
contributionOpposingSize
|
||||
)
|
||||
//2) peak population modifier
|
||||
//Towers should not be regarded as major battles.
|
||||
//As the population rises, the rewards decrease (dramatically).
|
||||
val populationModifier = FacilityHackParticipation.populationProgressModifier(
|
||||
opposingPopulationByLayer,
|
||||
victorPopulationByLayer,
|
||||
{ pop =>
|
||||
if (pop > 59) 0.75f
|
||||
else if (pop > 29) 0.675f
|
||||
else if (pop > 25) 0.55f
|
||||
else 0.5f
|
||||
if (pop > 80) 0f
|
||||
else if (pop > 39) (80 - pop).toFloat * 0.01f
|
||||
else if (pop > 25) 0.5f
|
||||
else if (pop > 19) 0.55f
|
||||
else if (pop > 9) 0.6f
|
||||
else if (pop > 5) 0.75f
|
||||
else 1f
|
||||
},
|
||||
2
|
||||
)
|
||||
//3) competition bonus
|
||||
//3) competition multiplier
|
||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||
victorPopulationByLayer,
|
||||
opposingPopulationByLayer,
|
||||
healthyPercentage = 1.25f
|
||||
)
|
||||
//4a. individual contribution factors - by time
|
||||
//Once again, an arbitrary five minute period.
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.45f)
|
||||
case (a, (_, d, _)) if d >= 300000 =>
|
||||
(a, 0.25f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.25f * (0.5f + (d.toFloat / 300000f)))
|
||||
case (a, (_, _, _)) =>
|
||||
(a, 0.15f)
|
||||
}
|
||||
//4b. individual contribution factors - by distance to goal (secondary_capture)
|
||||
//Because the hack duration of towers is instantaneous, distance from terminal is a more important factor
|
||||
val contributionPerPlayerByDistanceFromGoal = {
|
||||
var minDistance: Float = Float.PositiveInfinity
|
||||
val location = building
|
||||
.CaptureTerminal
|
||||
.map { terminal => terminal.Position }
|
||||
.getOrElse { hacker.Position }
|
||||
soiPlayers
|
||||
.map { p =>
|
||||
val distance = Vector3.Distance(p.Position, location)
|
||||
minDistance = math.min(minDistance, distance)
|
||||
(p.CharId, distance)
|
||||
}
|
||||
.map { case (id, distance) =>
|
||||
(id, math.max(0.15f, minDistance / distance))
|
||||
}
|
||||
}.toMap[Long, Float]
|
||||
//5) token competition bonus
|
||||
//This value will probably suck, and that's fine.
|
||||
val competitionBonus: Long = FacilityHackParticipation.competitionBonus(
|
||||
contributionVictorSize,
|
||||
contributionOpposingSize,
|
||||
|
|
@ -82,37 +128,29 @@ final case class TowerHackParticipation(building: Building) extends FacilityHack
|
|||
overwhelmingOddsPercentage = 0.5f,
|
||||
overwhelmingOddsBonus = 30L
|
||||
)
|
||||
//4) competition multiplier
|
||||
val competitionMultiplier: Float = FacilityHackParticipation.populationBalanceModifier(
|
||||
victorPopulationByLayer,
|
||||
opposingPopulationByLayer,
|
||||
healthyPercentage = 1.25f
|
||||
)
|
||||
//calculate overall command experience points and the individual player multiplier
|
||||
//6. calculate overall command experience points
|
||||
val finalCep: Long = math.ceil(
|
||||
math.max(1L, baseExperienceFromFacilityCapture + competitionBonus) *
|
||||
baseExperienceFromFacilityCapture *
|
||||
populationModifier *
|
||||
competitionMultiplier *
|
||||
Config.app.game.cepRate
|
||||
Config.app.game.experience.cep.rate + competitionBonus
|
||||
).toLong
|
||||
val contributionPerPlayerByTime = playerContribution.collect {
|
||||
case (a, (_, d, t)) if d >= 300000 && math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.5f)
|
||||
case (a, (_, d, t)) if math.abs(completionTime - t) < 5000 =>
|
||||
(a, 0.25f * (1f + (d.toFloat / 3000000f)))
|
||||
case (a, (_, d, t)) =>
|
||||
(a, 0.25f * (1f + (math.max(0, d - completionTime + t).toFloat / 3000000f)))
|
||||
}
|
||||
//reward participant(s)
|
||||
// classically, only players in the SOI are rewarded
|
||||
//7. reward participants
|
||||
//Classically, only players in the SOI are rewarded
|
||||
val events = building.Zone.AvatarEvents
|
||||
building.PlayersInSOI
|
||||
soiPlayers
|
||||
.filter { player =>
|
||||
player.Faction == victorFaction && player.CharId != hacker.CharId
|
||||
}
|
||||
.foreach { player =>
|
||||
val contributionMultiplier = contributionPerPlayerByTime.getOrElse(player.CharId, 1f)
|
||||
events ! AvatarServiceMessage(player.Name, AvatarAction.AwardCep(0, (finalCep * contributionMultiplier).toLong))
|
||||
val charId = player.CharId
|
||||
val contributionTimeMultiplier = contributionPerPlayerByTime.getOrElse(charId, 0.5f)
|
||||
val contributionDistanceMultiplier = contributionPerPlayerByDistanceFromGoal.getOrElse(charId, 0.5f)
|
||||
val outputValue = (finalCep * contributionTimeMultiplier * contributionDistanceMultiplier).toLong
|
||||
events ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.AwardCep(0, outputValue)
|
||||
)
|
||||
}
|
||||
events ! AvatarServiceMessage(hacker.Name, AvatarAction.AwardCep(hacker.CharId, finalCep))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
turret.ControlledWeapon(wepNumber = 1).foreach {
|
||||
case weapon: Tool =>
|
||||
// recharge when last shot fired 3s delay, +1, 200ms interval
|
||||
if (weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
|
||||
if (weapon.Magazine < weapon.MaxMagazine && System.currentTimeMillis() - weapon.LastDischarge > 3000L) {
|
||||
weapon.Magazine += 1
|
||||
val seat = turret.Seat(0).get
|
||||
seat.occupant match {
|
||||
|
|
|
|||
|
|
@ -187,8 +187,10 @@ trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
|
|||
val chargeToDeposit = if (min == 0) {
|
||||
transferTarget match {
|
||||
case Some(silo: ResourceSilo) =>
|
||||
// Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402)
|
||||
scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max)
|
||||
scala.math.min(
|
||||
scala.math.min(silo.MaxNtuCapacitor / silo.Definition.ChargeTime.toMillis.toFloat, chargeable.NtuCapacitor),
|
||||
max
|
||||
)
|
||||
case _ =>
|
||||
0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -893,7 +893,7 @@ object VehicleControl {
|
|||
|
||||
/**
|
||||
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
||||
* @param now the current time (in nanoseconds)
|
||||
* @param now the current time (in milliseconds)
|
||||
* @param act a `VitalsActivity` entry to test
|
||||
* @return `true`, if the shield charge would be blocked;
|
||||
* `false`, otherwise
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason, Su
|
|||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||
import net.psforever.types.{ExoSuitType, ImplantType, TransactionType}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
@ -327,7 +328,7 @@ trait InGameHistory {
|
|||
}
|
||||
|
||||
def GetContribution(): Option[List[InGameActivity]] = {
|
||||
Option(GetContributionDuringPeriod(History, duration = 600000L))
|
||||
Option(GetContributionDuringPeriod(History, duration = Config.app.game.experience.longContributionTime))
|
||||
}
|
||||
|
||||
def GetContributionDuringPeriod(list: List[InGameActivity], duration: Long): List[InGameActivity] = {
|
||||
|
|
|
|||
|
|
@ -78,23 +78,23 @@ object KillAssists {
|
|||
lastDamage: Option[DamageResult],
|
||||
history: Iterable[InGameActivity],
|
||||
): Seq[(PlayerSource, KDAStat)] = {
|
||||
val shortHistory = limitHistoryToThisLife(history.toList)
|
||||
determineKiller(lastDamage, shortHistory) match {
|
||||
val truncatedHistory = limitHistoryToThisLife(history.toList)
|
||||
determineKiller(lastDamage, truncatedHistory) match {
|
||||
case Some((result, killer: PlayerSource)) =>
|
||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, Some(killer))
|
||||
val fullBep = calculateExperience(killer, victim, shortHistory)
|
||||
val assists = collectKillAssistsForPlayer(victim, truncatedHistory, Some(killer))
|
||||
val fullBep = calculateExperience(killer, victim, truncatedHistory)
|
||||
val hitSquad = (killer, Kill(victim, result, fullBep)) +: assists.map {
|
||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||
}.toSeq
|
||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||
(victim, Death(hitSquad.map { _._1 }, truncatedHistory.last.time - truncatedHistory.head.time, fullBep)) +: hitSquad
|
||||
|
||||
case _ =>
|
||||
val assists = collectKillAssistsForPlayer(victim, shortHistory, None)
|
||||
val fullBep = Support.baseExperience(victim, shortHistory)
|
||||
val assists = collectKillAssistsForPlayer(victim, truncatedHistory, None)
|
||||
val fullBep = Support.baseExperience(victim, truncatedHistory)
|
||||
val hitSquad = assists.map {
|
||||
case ContributionStatsOutput(p, w, r) => (p, Assist(victim, w, r, (fullBep * r).toLong))
|
||||
}.toSeq
|
||||
(victim, Death(hitSquad.map { _._1 }, shortHistory.last.time - shortHistory.head.time, fullBep)) +: hitSquad
|
||||
(victim, Death(hitSquad.map { _._1 }, truncatedHistory.last.time - truncatedHistory.head.time, fullBep)) +: hitSquad
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.vital.projectile.ProjectileReason
|
|||
import net.psforever.objects.zones.exp.rec.{CombinedHealthAndArmorContributionProcess, MachineRecoveryExperienceContributionProcess}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
@ -49,7 +50,7 @@ object KillContributions {
|
|||
multivehicle_rearm_terminal,
|
||||
lodestar_repair_terminal
|
||||
).collect { _.ObjectId }
|
||||
} //TODO currently includes things that are not typical items but are used for expressing contribution implements
|
||||
} //TODO currently includes things that are not typical equipment but things that express contribution
|
||||
|
||||
/** cached for empty collection returns; please do not add anything to it */
|
||||
private val emptyMap: mutable.LongMap[ContributionStats] = mutable.LongMap.empty[ContributionStats]
|
||||
|
|
@ -119,21 +120,20 @@ object KillContributions {
|
|||
): Iterable[(Long, ContributionStatsOutput)] = {
|
||||
val faction = target.Faction
|
||||
/*
|
||||
divide into applicable time periods - long for 10 minutes and short 5 minutes;
|
||||
divide into applicable time periods;
|
||||
these two periods represent passes over the in-game history to evaluate statistic modification events;
|
||||
the short time period should stand on its own, but should also be represented in the long time period;
|
||||
more players should be rewarded if one qualifies for the longer time period's evaluation
|
||||
*/
|
||||
//divide by applicable time periods (long=10minutes, short=5minutes)
|
||||
val (contributions, (shortHistory, longHistory)) = {
|
||||
val (contributions, (longHistory, shortHistory)) = {
|
||||
val killTime = kill.time.toDate.getTime
|
||||
val shortPeriod = killTime - 300000L
|
||||
val shortPeriod = killTime - Config.app.game.experience.shortContributionTime
|
||||
val (contrib, onlyHistory) = history.partition { _.isInstanceOf[Contribution] }
|
||||
(
|
||||
contrib
|
||||
.collect { case Contribution(unique, entries) => (unique, entries) }
|
||||
.toMap[SourceUniqueness, List[InGameActivity]],
|
||||
limitHistoryToThisLife(onlyHistory.toList, killTime).partition { _.time > shortPeriod }
|
||||
limitHistoryToThisLife(onlyHistory.toList, killTime).partition { _.time < shortPeriod }
|
||||
)
|
||||
}
|
||||
//events that are older than 5 minutes are enough to prove one has been alive that long
|
||||
|
|
@ -195,7 +195,7 @@ object KillContributions {
|
|||
* @return the potentially truncated history
|
||||
*/
|
||||
private def limitHistoryToThisLife(history: List[InGameActivity], eventTime: Long): List[InGameActivity] = {
|
||||
limitHistoryToThisLife(history, eventTime, eventTime - 600000L)
|
||||
limitHistoryToThisLife(history, eventTime, eventTime - Config.app.game.experience.longContributionTime)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -262,9 +262,9 @@ object KillContributions {
|
|||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
||||
): mutable.LongMap[ContributionStats] = {
|
||||
contributeWithRevivalActivity(history, existingParticipants)
|
||||
contributeWithTerminalActivity(faction, history, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleTransportActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleCargoTransportActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithTerminalActivity(history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithVehicleCargoTransportActivity(kill, history, faction, contributions, excludedTargets, existingParticipants)
|
||||
contributeWithKillWhileMountedActivity(kill, faction, contributions, excludedTargets, existingParticipants)
|
||||
existingParticipants.remove(0)
|
||||
existingParticipants
|
||||
|
|
@ -314,13 +314,17 @@ object KillContributions {
|
|||
excludedTargets.addOne(owner)
|
||||
excludedTargets.addOne(attacker.unique)
|
||||
val time = kill.time.toDate.getTime
|
||||
val weaponStat = Support.calculateSupportExperience(
|
||||
event = "mounted-kill",
|
||||
WeaponStats(DriverAssist(mount.Definition.ObjectId), 1, 1, time, 1f)
|
||||
)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
owner.charId,
|
||||
ContributionStats(
|
||||
PlayerSource(owner, mount.Position),
|
||||
Seq(WeaponStats(DriverAssist(mount.Definition.ObjectId), 1, 1, time, 10f)),
|
||||
Seq(weaponStat),
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
|
|
@ -332,12 +336,12 @@ object KillContributions {
|
|||
}
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets, eventOutputType="support-repair")
|
||||
)
|
||||
case Some((mount: TurretSource, _: PlayerSource)) if !excludedTargets.contains(mount.unique) =>
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(mount, faction, eventTime, contributions, excludedTargets, eventOutputType="support-repair-turret")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -345,6 +349,7 @@ object KillContributions {
|
|||
/**
|
||||
* Gather and reward specific in-game equipment use activity.<br>
|
||||
* na
|
||||
* @param kill the in-game event that maintains information about the other player's death
|
||||
* @param history chronology of activity the game considers noteworthy
|
||||
* @param faction empire to target
|
||||
* @param contributions mapping between external entities
|
||||
|
|
@ -356,6 +361,7 @@ object KillContributions {
|
|||
* @see `extractContributionsForMachineByTarget`
|
||||
*/
|
||||
private def contributeWithVehicleTransportActivity(
|
||||
kill: Kill,
|
||||
history: List[InGameActivity],
|
||||
faction: PlanetSideEmpire.Value,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
|
|
@ -385,11 +391,19 @@ object KillContributions {
|
|||
}
|
||||
} || {
|
||||
val sameZone = in.zoneNumber == out.zoneNumber
|
||||
val distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceMoved = {
|
||||
val killLocation = kill.info.adversarial
|
||||
.collect { adversarial => adversarial.attacker.Position.xy }
|
||||
.getOrElse(Vector3.Zero)
|
||||
Vector3.DistanceSquared(killLocation, out.player.Position.xy)
|
||||
}
|
||||
val timeSpent = out.time - in.time
|
||||
timeSpent >= 210000 /* 3:30 */ ||
|
||||
(sameZone && (distanceMoved > 160000f || distanceMoved > 10000f && timeSpent >= 60000)) |
|
||||
(!sameZone && (distanceMoved > 10000f || timeSpent >= 120000))
|
||||
distanceMoved < 5625f /* 75m */ &&
|
||||
(timeSpent >= 210000L /* 3:30 */ ||
|
||||
(sameZone && (distanceTransported > 160000f /* 400m */ ||
|
||||
distanceTransported > 10000f /* 100m */ && timeSpent >= 60000L /* 1:00m */)) ||
|
||||
(!sameZone && (distanceTransported > 10000f /* 100m */ || timeSpent >= 120000L /* 2:00 */ )))
|
||||
}) =>
|
||||
out
|
||||
}
|
||||
|
|
@ -398,18 +412,23 @@ object KillContributions {
|
|||
.groupBy { _.vehicle }
|
||||
.collect { case (mount, dismountsFromVehicle) if mount.owner.nonEmpty =>
|
||||
val promotedOwner = PlayerSource(mount.owner.get, mount.Position)
|
||||
val equipmentUseContext = mount.Definition match {
|
||||
val (equipmentUseContext, equipmentUseEvent) = mount.Definition match {
|
||||
case v @ GlobalDefinitions.router =>
|
||||
RouterKillAssist(v.ObjectId)
|
||||
(RouterKillAssist(v.ObjectId), "router")
|
||||
case v =>
|
||||
HotDropKillAssist(v.ObjectId, 0)
|
||||
(HotDropKillAssist(v.ObjectId, 0), "hotdrop")
|
||||
}
|
||||
val statContext = Seq(WeaponStats(equipmentUseContext, 0, dismountsFromVehicle.size, dismountsFromVehicle.maxBy(_.time).time, 15f))
|
||||
val size = dismountsFromVehicle.size
|
||||
val time = dismountsFromVehicle.maxBy(_.time).time
|
||||
val weaponStat = Support.calculateSupportExperience(
|
||||
equipmentUseEvent,
|
||||
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||
)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
promotedOwner.CharId,
|
||||
ContributionStats(promotedOwner, statContext, 1, 1, 1, statContext.head.time)
|
||||
ContributionStats(promotedOwner, Seq(weaponStat), size, size, size, time)
|
||||
)
|
||||
)
|
||||
contributions.get(mount.unique).collect {
|
||||
|
|
@ -417,13 +436,13 @@ object KillContributions {
|
|||
val mountHistory = dismountsFromVehicle
|
||||
.flatMap { event =>
|
||||
val eventTime = event.time
|
||||
val startTime = event.pairedEvent.get.time - 600000L
|
||||
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||
limitHistoryToThisLife(list, eventTime, startTime)
|
||||
}
|
||||
.distinctBy(_.time)
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -432,6 +451,7 @@ object KillContributions {
|
|||
/**
|
||||
* Gather and reward specific in-game equipment use activity.<br>
|
||||
* na
|
||||
* @param kill the in-game event that maintains information about the other player's death
|
||||
* @param faction empire to target
|
||||
* @param contributions mapping between external entities
|
||||
* the target has interacted with in the form of in-game activity
|
||||
|
|
@ -442,6 +462,7 @@ object KillContributions {
|
|||
* @see `extractContributionsForMachineByTarget`
|
||||
*/
|
||||
private def contributeWithVehicleCargoTransportActivity(
|
||||
kill: Kill,
|
||||
history: List[InGameActivity],
|
||||
faction: PlanetSideEmpire.Value,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
|
|
@ -463,9 +484,16 @@ object KillContributions {
|
|||
if in.vehicle.unique == out.vehicle.unique &&
|
||||
out.vehicle.Faction == out.cargo.Faction &&
|
||||
(in.vehicle.Definition == GlobalDefinitions.router || {
|
||||
val distanceMoved = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceTransported = Vector3.DistanceSquared(in.vehicle.Position.xy, out.vehicle.Position.xy)
|
||||
val distanceMoved = {
|
||||
val killLocation = kill.info.adversarial
|
||||
.collect { adversarial => adversarial.attacker.Position.xy }
|
||||
.getOrElse(Vector3.Zero)
|
||||
Vector3.DistanceSquared(killLocation, out.cargo.Position.xy)
|
||||
}
|
||||
val timeSpent = out.time - in.time
|
||||
timeSpent >= 210000 /* 3:30 */ || distanceMoved > 640000f
|
||||
distanceMoved < 5625f /* 75m */ &&
|
||||
(timeSpent >= 210000 /* 3:30 */ || distanceTransported > 360000f /* 600m */)
|
||||
}) =>
|
||||
out
|
||||
}
|
||||
|
|
@ -478,7 +506,13 @@ object KillContributions {
|
|||
dismountsFromVehicle
|
||||
.groupBy(_.vehicle)
|
||||
.map { case (vehicle, events) =>
|
||||
(vehicle, vehicle.owner, Seq(WeaponStats(HotDropKillAssist(vehicle.Definition.ObjectId, mountId), 0, events.size, events.maxBy(_.time).time, 15f)))
|
||||
val size = events.size
|
||||
val time = events.maxBy(_.time).time
|
||||
val weaponStat = Support.calculateSupportExperience(
|
||||
event = "hotdrop",
|
||||
WeaponStats(HotDropKillAssist(vehicle.Definition.ObjectId, mountId), size, size, time, 1f)
|
||||
)
|
||||
(vehicle, vehicle.owner, Seq(weaponStat))
|
||||
}
|
||||
.collect { case (vehicle, Some(owner), statContext) =>
|
||||
combineStatsInto(
|
||||
|
|
@ -493,13 +527,13 @@ object KillContributions {
|
|||
val mountHistory = dismountsFromVehicle
|
||||
.flatMap { event =>
|
||||
val eventTime = event.time
|
||||
val startTime = event.pairedEvent.get.time - 600000L
|
||||
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||
limitHistoryToThisLife(list, eventTime, startTime)
|
||||
}
|
||||
.distinctBy(_.time)
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(mount, faction, mountHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||
)
|
||||
}
|
||||
contributions.get(vehicle.unique).collect {
|
||||
|
|
@ -507,13 +541,13 @@ object KillContributions {
|
|||
val carrierHistory = dismountsFromVehicle
|
||||
.flatMap { event =>
|
||||
val eventTime = event.time
|
||||
val startTime = event.pairedEvent.get.time - 600000L
|
||||
val startTime = event.pairedEvent.get.time - Config.app.game.experience.longContributionTime
|
||||
limitHistoryToThisLife(list, eventTime, startTime)
|
||||
}
|
||||
.distinctBy(_.time)
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(vehicle, faction, carrierHistory, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(vehicle, faction, carrierHistory, contributions, excludedTargets, eventOutputType="support-repair")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -542,104 +576,120 @@ object KillContributions {
|
|||
* @see `TerminalUsedActivity`
|
||||
*/
|
||||
private def contributeWithTerminalActivity(
|
||||
faction: PlanetSideEmpire.Value,
|
||||
history: List[InGameActivity],
|
||||
faction: PlanetSideEmpire.Value,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||
out: mutable.LongMap[ContributionStats]
|
||||
): Unit = {
|
||||
val data = history
|
||||
history
|
||||
.collect {
|
||||
case h: HealFromTerminal => (h.term, (h, h.term.hacked))
|
||||
case r: RepairFromTerminal => (r.term, (r, r.term.hacked))
|
||||
case t: TerminalUsedActivity => (t.terminal, (t, t.terminal.hacked))
|
||||
case h: HealFromTerminal => (h.term, h)
|
||||
case r: RepairFromTerminal => (r.term, r)
|
||||
case t: TerminalUsedActivity => (t.terminal, t)
|
||||
}
|
||||
.groupBy(_._1.unique)
|
||||
.map { case (_, list) => (list.head._1, list.map { _._2 }) }
|
||||
data.flatMap {
|
||||
case (terminal, events) =>
|
||||
val (activity, hackState) = events.unzip
|
||||
val terminalFaction = terminal.Faction
|
||||
if (terminalFaction != faction && hackState.exists { _.nonEmpty }) {
|
||||
/*
|
||||
if the terminal has been hacked,
|
||||
and the original terminal does not align with our own faction,
|
||||
then the support must be reported as a hack;
|
||||
if we are the same faction as the terminal, then the hacked condition is irrelevant
|
||||
*/
|
||||
val hackContext = HackKillAssist(GlobalDefinitions.remote_electronics_kit.ObjectId, terminal.Definition.ObjectId)
|
||||
hackState
|
||||
.groupBy(_.get.player)
|
||||
.collect {
|
||||
case (player, _) if player.Faction == faction => //only reward allied hacking
|
||||
val time = activity.maxBy(_.time).time
|
||||
.map {
|
||||
case (_, events1) =>
|
||||
val (termThings1, _) = events1.unzip
|
||||
val hackContext = HackKillAssist(GlobalDefinitions.remote_electronics_kit.ObjectId, termThings1.head.Definition.ObjectId)
|
||||
if (termThings1.exists(t => t.Faction != faction && t.hacked.nonEmpty)) {
|
||||
/*
|
||||
if the terminal has been hacked,
|
||||
and the original terminal does not align with our own faction,
|
||||
then the support must be reported as a hack;
|
||||
if we are the same faction as the terminal, then the hacked condition is irrelevant
|
||||
*/
|
||||
events1
|
||||
.collect { case out @ (t, _) if t.hacked.nonEmpty => out }
|
||||
.groupBy { case (t, _) => t.hacked.get.player.unique }
|
||||
.foreach { case (_, events2) =>
|
||||
val (termThings2, events3) = events2.unzip
|
||||
val hacker = termThings2.head.hacked.get.player
|
||||
val size = events3.size
|
||||
val time = events3.maxBy(_.time).time
|
||||
val weaponStats = Support.calculateSupportExperience(
|
||||
event = "hack",
|
||||
WeaponStats(hackContext, size, size, time, 1f)
|
||||
)
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
player.CharId,
|
||||
hacker.CharId,
|
||||
ContributionStats(
|
||||
player,
|
||||
Seq(WeaponStats(hackContext, 0, 1, time, 10f)),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
hacker,
|
||||
Seq(weaponStats),
|
||||
size,
|
||||
size,
|
||||
size,
|
||||
time
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
activity
|
||||
} else if (terminalFaction == faction) {
|
||||
val eventTime = activity.maxBy(_.time).time
|
||||
val startTime = activity.minBy(_.time).time - 600000L
|
||||
val (equipmentUseContext, ownerOpt) = terminal.installation match {
|
||||
case v: VehicleSource =>
|
||||
terminal.Definition match {
|
||||
case GlobalDefinitions.order_terminala =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
|
||||
case GlobalDefinitions.order_terminalb =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(AmsResupplyKillAssist(terminal.Definition.ObjectId), v.owner)
|
||||
case GlobalDefinitions.lodestar_repair_terminal =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(RepairKillAssist(terminal.Definition.ObjectId, v.Definition.ObjectId), v.owner)
|
||||
case GlobalDefinitions.bfr_rearm_terminal =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
|
||||
case GlobalDefinitions.multivehicle_rearm_terminal =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(v, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(LodestarRearmKillAssist(terminal.Definition.ObjectId), v.owner)
|
||||
case _ =>
|
||||
(NoUse(), None)
|
||||
}
|
||||
case _: BuildingSource =>
|
||||
combineStatsInto(out, extractContributionsForMachineByTarget(terminal, faction, eventTime, startTime, contributions, excludedTargets))
|
||||
(NoUse(), None) //general terminal use
|
||||
case _ =>
|
||||
(NoUse(), None)
|
||||
}
|
||||
ownerOpt.collect { owner =>
|
||||
val time = activity.maxBy(_.time).time
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
owner.charId,
|
||||
ContributionStats(
|
||||
PlayerSource(owner, terminal.installation.Position),
|
||||
Seq(WeaponStats(equipmentUseContext, 0, 1, time, 10f)),
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
time
|
||||
} else if (termThings1.exists(_.Faction == faction)) {
|
||||
//faction-aligned terminal
|
||||
val (_, events2) = events1.unzip
|
||||
val eventTime = events2.maxBy(_.time).time
|
||||
val startTime = events2.minBy(_.time).time - Config.app.game.experience.longContributionTime
|
||||
val termThingsHead = termThings1.head
|
||||
val (equipmentUseContext, equipmentUseEvent, installationEvent, target) = termThingsHead.installation match {
|
||||
case v: VehicleSource =>
|
||||
termThingsHead.Definition match {
|
||||
case GlobalDefinitions.order_terminala =>
|
||||
(AmsResupplyKillAssist(GlobalDefinitions.order_terminala.ObjectId), "ams-resupply", "support-repair", Some(v))
|
||||
case GlobalDefinitions.order_terminalb =>
|
||||
(AmsResupplyKillAssist(GlobalDefinitions.order_terminalb.ObjectId), "ams-resupply", "support-repair", Some(v))
|
||||
case GlobalDefinitions.lodestar_repair_terminal =>
|
||||
(RepairKillAssist(GlobalDefinitions.lodestar_repair_terminal.ObjectId, v.Definition.ObjectId), "lodestar-repair", "support-repair", Some(v))
|
||||
case GlobalDefinitions.bfr_rearm_terminal =>
|
||||
(LodestarRearmKillAssist(GlobalDefinitions.bfr_rearm_terminal.ObjectId), "lodestar-rearm", "support-repair", Some(v))
|
||||
case GlobalDefinitions.multivehicle_rearm_terminal =>
|
||||
(LodestarRearmKillAssist(GlobalDefinitions.multivehicle_rearm_terminal.ObjectId), "lodestar-rearm", "support-repair", Some(v))
|
||||
case _ =>
|
||||
(NoUse(), "", "", None)
|
||||
}
|
||||
case _: BuildingSource =>
|
||||
(NoUse(), "", "support-repair-terminal", Some(termThingsHead))
|
||||
case _ =>
|
||||
(NoUse(), "", "", None)
|
||||
}
|
||||
target.map { src =>
|
||||
combineStatsInto(
|
||||
out,
|
||||
extractContributionsForMachineByTarget(src, faction, eventTime, startTime, contributions, excludedTargets, installationEvent)
|
||||
)
|
||||
}
|
||||
events1
|
||||
.map { case (a, b) => (a.installation, b) }
|
||||
.collect { case (installation: VehicleSource, evt) if installation.owner.nonEmpty => (installation, evt) }
|
||||
.groupBy(_._1.owner.get)
|
||||
.collect { case (owner, list) =>
|
||||
val (installations, events2) = list.unzip
|
||||
val size = events2.size
|
||||
val time = events2.maxBy(_.time).time
|
||||
val weaponStats = Support.calculateSupportExperience(
|
||||
equipmentUseEvent,
|
||||
WeaponStats(equipmentUseContext, size, size, time, 1f)
|
||||
)
|
||||
))
|
||||
combineStatsInto(
|
||||
out,
|
||||
(
|
||||
owner.charId,
|
||||
ContributionStats(
|
||||
PlayerSource(owner, installations.head.Position),
|
||||
Seq(weaponStats),
|
||||
size,
|
||||
size,
|
||||
size,
|
||||
time
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
activity
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -671,7 +721,12 @@ object KillContributions {
|
|||
id,
|
||||
ContributionStats(
|
||||
user,
|
||||
Seq(WeaponStats(ReviveKillAssist(objectId), 100, 1, time, math.log(eventSize + 1).toFloat * 15f)),
|
||||
Seq({
|
||||
Support.calculateSupportExperience(
|
||||
event = "revival",
|
||||
WeaponStats(ReviveKillAssist(objectId), 1, eventSize, time, 1f)
|
||||
)
|
||||
}),
|
||||
eventSize,
|
||||
eventSize,
|
||||
eventSize,
|
||||
|
|
@ -700,10 +755,11 @@ object KillContributions {
|
|||
faction: PlanetSideEmpire.Value,
|
||||
time: Long,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||
eventOutputType: String
|
||||
): mutable.LongMap[ContributionStats] = {
|
||||
val start: Long = time - 600000L
|
||||
extractContributionsForMachineByTarget(target, faction, time, start, contributions, excludedTargets)
|
||||
val start: Long = time - Config.app.game.experience.longContributionTime
|
||||
extractContributionsForMachineByTarget(target, faction, time, start, contributions, excludedTargets, eventOutputType)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -726,11 +782,12 @@ object KillContributions {
|
|||
eventTime: Long,
|
||||
startTime: Long,
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||
eventOutputType: String
|
||||
): mutable.LongMap[ContributionStats] = {
|
||||
val unique = target.unique
|
||||
val history = limitHistoryToThisLife(contributions.getOrElse(unique, List()), eventTime, startTime)
|
||||
extractContributionsForMachineByTarget(target, faction, history, contributions, excludedTargets)
|
||||
extractContributionsForMachineByTarget(target, faction, history, contributions, excludedTargets, eventOutputType)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -753,12 +810,13 @@ object KillContributions {
|
|||
faction: PlanetSideEmpire.Value,
|
||||
history: List[InGameActivity],
|
||||
contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness]
|
||||
excludedTargets: mutable.ListBuffer[SourceUniqueness],
|
||||
eventOutputType: String
|
||||
): mutable.LongMap[ContributionStats] = {
|
||||
val unique = target.unique
|
||||
if (!excludedTargets.contains(unique) && history.nonEmpty) {
|
||||
excludedTargets.addOne(unique)
|
||||
val process = new MachineRecoveryExperienceContributionProcess(faction, contributions, excludedTargets)
|
||||
val process = new MachineRecoveryExperienceContributionProcess(faction, contributions, eventOutputType, excludedTargets)
|
||||
process.submit(history)
|
||||
cullContributorImplements(process.output())
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package net.psforever.objects.zones.exp
|
|||
import net.psforever.objects.sourcing.PlayerSource
|
||||
import net.psforever.objects.vital.{InGameActivity, ReconstructionActivity, RepairFromExoSuitChange, SpawningActivity}
|
||||
import net.psforever.types.{ExoSuitType, PlanetSideEmpire}
|
||||
import net.psforever.util.Config
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
@ -11,6 +12,8 @@ import scala.collection.mutable
|
|||
* Functions to assist experience calculation and history manipulation and analysis.
|
||||
*/
|
||||
object Support {
|
||||
private val sep = Config.app.game.experience.sep
|
||||
|
||||
/**
|
||||
* Calculate a base experience value to consider additional reasons for points.
|
||||
* @param victim player to which a final interaction has reduced health to zero
|
||||
|
|
@ -27,18 +30,19 @@ object Support {
|
|||
case _ => 0L
|
||||
}
|
||||
val base = if (Support.wasEverAMax(victim, history)) {
|
||||
250L
|
||||
} else if (victim.Seated || victim.progress.kills.nonEmpty) {
|
||||
100L
|
||||
Config.app.game.experience.bep.base.asMax
|
||||
} else if (victim.progress.kills.nonEmpty) {
|
||||
Config.app.game.experience.bep.base.withKills
|
||||
} else if (victim.Seated) {
|
||||
Config.app.game.experience.bep.base.asMounted
|
||||
} else if (lifespan > 15000L) {
|
||||
50L
|
||||
Config.app.game.experience.bep.base.mature
|
||||
} else {
|
||||
1L
|
||||
}
|
||||
if (base > 1) {
|
||||
//black ops modifier
|
||||
//TODO x10
|
||||
base
|
||||
base * Config.app.game.experience.bep.base.bopsMultiplier
|
||||
} else {
|
||||
base
|
||||
}
|
||||
|
|
@ -187,4 +191,49 @@ object Support {
|
|||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a weapon statistics entry and calculate the support experience value resulting from this support event.
|
||||
* The complete formula is:<br><br>
|
||||
* `base + shots-multplier * ln(shots^exp + 2) + amount-multiplier * amount`<br><br>
|
||||
* ... where the middle field can be truncated into:<br><br>
|
||||
* `shots-multplier * shots`<br><br>
|
||||
* ... without the natural logarithm exponent defined.
|
||||
* Limits can be applied to the number of shots and/or to the amount,
|
||||
* which will either zero the calculations or cap the results.
|
||||
* @param event identification for the event calculation parameters
|
||||
* @param weaponStat base weapon stat entry to be modified
|
||||
* @param canNotFindEventDefaultValue custom default value
|
||||
* @return weapon stat entry with a modified for the experience
|
||||
*/
|
||||
private[exp] def calculateSupportExperience(
|
||||
event: String,
|
||||
weaponStat: WeaponStats,
|
||||
canNotFindEventDefaultValue: Option[Float] = None
|
||||
): WeaponStats = {
|
||||
val rewards: Float = sep.events
|
||||
.find(evt => event.equals(evt.name))
|
||||
.map { event =>
|
||||
val shots = weaponStat.shots
|
||||
val shotsMultiplier = event.shotsMultiplier
|
||||
if (shotsMultiplier > 0f && shots < event.shotsCutoff) {
|
||||
val modifiedShotsReward: Float = {
|
||||
val partialShots = math.min(event.shotsLimit, shots).toFloat
|
||||
shotsMultiplier * (if (event.shotsNatLog > 0f) {
|
||||
math.log(math.pow(partialShots, event.shotsNatLog) + 2d).toFloat
|
||||
} else {
|
||||
partialShots
|
||||
})
|
||||
}
|
||||
val modifiedAmountReward: Float = event.amountMultiplier * weaponStat.amount.toFloat
|
||||
event.base + modifiedShotsReward + modifiedAmountReward
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
}
|
||||
.getOrElse(
|
||||
canNotFindEventDefaultValue.getOrElse(sep.canNotFindEventDefaultValue.toFloat)
|
||||
)
|
||||
weaponStat.copy(contributions = rewards)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
package net.psforever.objects.zones.exp
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
import net.psforever.objects.avatar.scoring.EquipmentStat
|
||||
import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
|
||||
import net.psforever.objects.sourcing.VehicleSource
|
||||
|
|
@ -11,8 +10,6 @@ import net.psforever.types.Vector3
|
|||
import net.psforever.util.Database.ctx
|
||||
import net.psforever.util.Database.ctx._
|
||||
|
||||
import scala.util.Success
|
||||
|
||||
object ToDatabase {
|
||||
/**
|
||||
* Insert an entry into the database's `killactivity` table.
|
||||
|
|
@ -57,7 +54,7 @@ object ToDatabase {
|
|||
position: Vector3,
|
||||
exp: Long
|
||||
): Unit = {
|
||||
ctx.run(query[persistence.Assistactivity]
|
||||
ctx.run(query[persistence.Killactivity]
|
||||
.insert(
|
||||
_.killerId -> lift(avatarId),
|
||||
_.victimId -> lift(victimId),
|
||||
|
|
@ -115,6 +112,10 @@ object ToDatabase {
|
|||
_.assists -> lift(0),
|
||||
_.sessionId -> lift(-1L)
|
||||
)
|
||||
.onConflictUpdate(_.avatarId, _.weaponId, _.sessionId)(
|
||||
(t, e) => t.shotsFired -> (t.shotsFired + e.shotsFired),
|
||||
(t, e) => t.shotsLanded -> (t.shotsLanded + e.shotsLanded)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -151,4 +152,66 @@ object ToDatabase {
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an entry into the database's `ntuactivity` table.
|
||||
* This table monitors experience earned through NTU silo operations and
|
||||
* first time event entity interactions (zone and building set to 0).
|
||||
*/
|
||||
def reportNtuActivity(
|
||||
avatarId: Long,
|
||||
zoneId: Int,
|
||||
buildingId: Int,
|
||||
experience: Long
|
||||
): Unit = {
|
||||
ctx.run(query[persistence.Ntuactivity]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.zoneId -> lift(zoneId),
|
||||
_.buildingId -> lift(buildingId),
|
||||
_.exp -> lift(experience)
|
||||
)
|
||||
.onConflictUpdate(_.avatarId, _.zoneId, _.buildingId)(
|
||||
(t, e) => t.exp -> (t.exp + e.exp)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an entry into the database's `kdasession` table
|
||||
* to specifically update the revive counter for the current session.
|
||||
*/
|
||||
def reportRespawns(
|
||||
avatarId: Long,
|
||||
reviveCount: Int
|
||||
): Unit = {
|
||||
ctx.run(query[persistence.Kdasession]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.revives -> lift(reviveCount),
|
||||
_.sessionId -> lift(-1)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an entry into the database's `buildingCapture` table.
|
||||
*/
|
||||
def reportFacilityCapture(
|
||||
avatarId: Long,
|
||||
zoneId: Int,
|
||||
buildingId: Int,
|
||||
exp: Long,
|
||||
expType: String
|
||||
): Unit = {
|
||||
ctx.run(query[persistence.Buildingcapture]
|
||||
.insert(
|
||||
_.avatarId -> lift(avatarId),
|
||||
_.zoneId -> lift(zoneId),
|
||||
_.buildingId -> lift(buildingId),
|
||||
_.exp -> lift(exp),
|
||||
_.expType -> lift(expType)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.zones.exp.rec
|
|||
|
||||
import net.psforever.objects.sourcing.SourceUniqueness
|
||||
import net.psforever.objects.vital.InGameActivity
|
||||
import net.psforever.objects.zones.exp.{ContributionStats, KillContributions, WeaponStats}
|
||||
import net.psforever.objects.zones.exp.{ContributionStats, KillContributions, Support, WeaponStats}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.collection.mutable
|
||||
|
|
@ -58,23 +58,32 @@ class CombinedHealthAndArmorContributionProcess(
|
|||
}
|
||||
.map {
|
||||
case (id, entry) =>
|
||||
var totalShots: Int = 0
|
||||
var totalAmount: Int = 0
|
||||
var mostRecentTime: Long = 0
|
||||
val groupedWeapons = entry.weapons
|
||||
.groupBy(_.equipment)
|
||||
.map {
|
||||
case (weaponId, weaponEntries) =>
|
||||
val specificEntries = weaponEntries.filter(_.equipment == weaponId)
|
||||
case (weaponContext, weaponEntries) =>
|
||||
val specificEntries = weaponEntries.filter(_.equipment == weaponContext)
|
||||
val amount = specificEntries.foldLeft(0)(_ + _.amount)
|
||||
totalAmount = totalAmount + amount
|
||||
val shots = specificEntries.foldLeft(0)(_ + _.shots)
|
||||
WeaponStats(weaponId, amount, shots, specificEntries.maxBy(_.time).time, 1f)
|
||||
totalShots = totalShots + shots
|
||||
val time = specificEntries.maxBy(_.time).time
|
||||
mostRecentTime = math.max(mostRecentTime, time)
|
||||
Support.calculateSupportExperience(
|
||||
event = "support-heal",
|
||||
WeaponStats(weaponContext, amount, shots, time, 1f)
|
||||
)
|
||||
}
|
||||
.toSeq
|
||||
(id, ContributionStats(
|
||||
player = entry.player,
|
||||
(id, entry.copy(
|
||||
weapons = groupedWeapons,
|
||||
amount = entry.amount + entry.amount,
|
||||
total = entry.total + entry.total,
|
||||
shots = groupedWeapons.foldLeft(0)(_ + _.shots),
|
||||
time = groupedWeapons.maxBy(_.time).time
|
||||
amount = totalAmount,
|
||||
total = math.max(entry.total, totalAmount),
|
||||
shots = totalShots,
|
||||
time = mostRecentTime
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ package net.psforever.objects.zones.exp.rec
|
|||
|
||||
import net.psforever.objects.sourcing.SourceUniqueness
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, RepairFromEquipment, RepairingActivity}
|
||||
import net.psforever.objects.zones.exp.ContributionStats
|
||||
import net.psforever.objects.zones.exp.{ContributionStats, Support, WeaponStats}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
class MachineRecoveryExperienceContributionProcess(
|
||||
private val faction : PlanetSideEmpire.Value,
|
||||
private val contributions: Map[SourceUniqueness, List[InGameActivity]],
|
||||
eventOutputType: String,
|
||||
private val excludedTargets: mutable.ListBuffer[SourceUniqueness] = mutable.ListBuffer()
|
||||
) extends RecoveryExperienceContributionProcess(faction, contributions) {
|
||||
def submit(history: List[InGameActivity]): Unit = {
|
||||
|
|
@ -65,11 +65,9 @@ class MachineRecoveryExperienceContributionProcess(
|
|||
.map { case (wrapper, entries) =>
|
||||
val size = entries.size
|
||||
val newTime = entries.maxBy(_.time).time
|
||||
entries.head.copy(
|
||||
shots = size,
|
||||
amount = entries.foldLeft(0)(_ + _.amount),
|
||||
contributions = (10 + size).toFloat,
|
||||
time = newTime
|
||||
Support.calculateSupportExperience(
|
||||
eventOutputType,
|
||||
WeaponStats(wrapper, size, entries.foldLeft(0)(_ + _.amount), newTime, 1f)
|
||||
)
|
||||
}
|
||||
.toSeq
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Assistactivity(
|
||||
index: Int,
|
||||
killerId: Long,
|
||||
victimId: Long,
|
||||
weaponId: Int,
|
||||
zoneId: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
95
src/main/scala/net/psforever/persistence/KdaExp.scala
Normal file
95
src/main/scala/net/psforever/persistence/KdaExp.scala
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// Copyright (c) 2023 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Assistactivity(
|
||||
index: Int,
|
||||
killerId: Long,
|
||||
victimId: Long,
|
||||
weaponId: Int,
|
||||
zoneId: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
||||
case class Buildingcapture(
|
||||
index: Int,
|
||||
avatarId: Long,
|
||||
zoneId: Int,
|
||||
buildingId: Int,
|
||||
exp: Long,
|
||||
expType: String,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
||||
case class Kdasession (
|
||||
avatarId: Long,
|
||||
sessionId: Int,
|
||||
kills: Int,
|
||||
deaths: Int,
|
||||
assists: Int,
|
||||
revives: Int
|
||||
)
|
||||
|
||||
case class Killactivity(
|
||||
index: Int,
|
||||
killerId: Long,
|
||||
victimId: Long,
|
||||
victimExosuit: Int,
|
||||
victimMounted: Int, //object type id * 10 + seat type
|
||||
weaponId: Int,
|
||||
zoneId: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
||||
case class Machinedestroyed(
|
||||
index: Int,
|
||||
avatarId: Long,
|
||||
weaponId: Int,
|
||||
machineType: Int,
|
||||
machineFaction: Int,
|
||||
hackedFaction: Int,
|
||||
asCargo: Boolean,
|
||||
zoneNum: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
||||
case class Ntuactivity (
|
||||
avatarId: Long,
|
||||
zoneId: Int,
|
||||
buildingId: Int,
|
||||
exp: Long
|
||||
)
|
||||
|
||||
case class Supportactivity(
|
||||
index: Int,
|
||||
userId: Long,
|
||||
targetId: Long,
|
||||
targetExosuit: Int,
|
||||
interactionType: Int,
|
||||
implementType: Int,
|
||||
intermediateType: Int,
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
||||
case class Weaponstatsession(
|
||||
avatarId: Long,
|
||||
weaponId: Int,
|
||||
shotsFired: Int,
|
||||
shotsLanded: Int,
|
||||
kills: Int,
|
||||
assists: Int,
|
||||
sessionId: Long
|
||||
)
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) 2022 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Killactivity(
|
||||
index: Int,
|
||||
killerId: Long,
|
||||
victimId: Long,
|
||||
victimExosuit: Int,
|
||||
victimMounted: Int, //object type id * 10 + seat type
|
||||
weaponId: Int,
|
||||
zoneId: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
// Copyright (c) 2022 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Machinedestroyed(
|
||||
index: Int,
|
||||
avatarId: Long,
|
||||
weaponId: Int,
|
||||
machineType: Int,
|
||||
machineFaction: Int,
|
||||
hackedFaction: Int,
|
||||
asCargo: Boolean,
|
||||
zoneNum: Int,
|
||||
px: Int, //Position.x * 1000
|
||||
py: Int, //Position.y * 1000
|
||||
pz: Int, //Position.z * 1000
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) 2022 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
case class Supportactivity(
|
||||
index: Int,
|
||||
userId: Long,
|
||||
targetId: Long,
|
||||
targetExosuit: Int,
|
||||
interactionType: Int,
|
||||
implementType: Int,
|
||||
intermediateType: Int,
|
||||
exp: Long,
|
||||
timestamp: LocalDateTime = LocalDateTime.now()
|
||||
)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
// Copyright (c) 2022 PSForever
|
||||
package net.psforever.persistence
|
||||
|
||||
case class Weaponstatsession(
|
||||
avatarId: Long,
|
||||
weaponId: Int,
|
||||
shotsFired: Int,
|
||||
shotsLanded: Int,
|
||||
kills: Int,
|
||||
assists: Int,
|
||||
sessionId: Long
|
||||
)
|
||||
|
|
@ -82,7 +82,7 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
|||
entryManagementBehaviors
|
||||
.orElse {
|
||||
case RemoverActor.AddTask(obj, zone, duration) =>
|
||||
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toNanos)
|
||||
val entry = RemoverActor.Entry(obj, zone, duration.getOrElse(FirstStandardDuration).toMillis)
|
||||
if (InclusionTest(entry) && !secondHeap.exists(test => sameEntryComparator.Test(test, entry))) {
|
||||
InitialJob(entry)
|
||||
if (entry.duration == 0) {
|
||||
|
|
@ -120,7 +120,7 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
|||
case RemoverActor.StartDelete() =>
|
||||
firstTask.cancel()
|
||||
secondTask.cancel()
|
||||
val now: Long = System.nanoTime
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val (in, out) = firstHeap.partition(entry => {
|
||||
now - entry.time >= entry.duration
|
||||
})
|
||||
|
|
@ -229,19 +229,19 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
|||
* this new entry is always set to last for the duration of the second pool
|
||||
*/
|
||||
private def RepackageEntry(entry: RemoverActor.Entry): RemoverActor.Entry = {
|
||||
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toNanos)
|
||||
RemoverActor.Entry(entry.obj, entry.zone, SecondStandardDuration.toMillis)
|
||||
}
|
||||
|
||||
/**
|
||||
* Common function to reset the first task's delayed execution.
|
||||
* Cancels the scheduled timer and will only restart the timer if there is at least one entry in the first pool.
|
||||
* @param now the time (in nanoseconds);
|
||||
* defaults to the current time (in nanoseconds)
|
||||
* @param now the time (in milliseconds);
|
||||
* defaults to the current time (in milliseconds)
|
||||
*/
|
||||
def RetimeFirstTask(now: Long = System.nanoTime): Unit = {
|
||||
def RetimeFirstTask(now: Long = System.currentTimeMillis()): Unit = {
|
||||
firstTask.cancel()
|
||||
if (firstHeap.nonEmpty) {
|
||||
val short_timeout: FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)) nanoseconds
|
||||
val short_timeout: FiniteDuration = math.max(1, firstHeap.head.duration - (now - firstHeap.head.time)).milliseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
firstTask = context.system.scheduler.scheduleOnce(short_timeout, self, RemoverActor.StartDelete())
|
||||
}
|
||||
|
|
@ -274,14 +274,14 @@ abstract class RemoverActor() extends SupportActor[RemoverActor.Entry] {
|
|||
/**
|
||||
* Default time for entries waiting in the first list.
|
||||
* Override.
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into milliseconds)
|
||||
*/
|
||||
def FirstStandardDuration: FiniteDuration
|
||||
|
||||
/**
|
||||
* Default time for entries waiting in the second list.
|
||||
* Override.
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into nanoseconds)
|
||||
* @return the time as a `FiniteDuration` object (to be later transformed into milliseconds)
|
||||
*/
|
||||
def SecondStandardDuration: FiniteDuration
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ object RemoverActor extends SupportActorCaseConversions {
|
|||
* Internally, all entries have a "time created" field.
|
||||
* @param _obj the target
|
||||
* @param _zone the zone in which this target is registered
|
||||
* @param _duration how much longer the target will exist in its current state (in nanoseconds)
|
||||
* @param _duration how much longer the target will exist in its current state (in milliseconds)
|
||||
*/
|
||||
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, _duration: Long)
|
||||
extends SupportActor.Entry(_obj, _zone, _duration)
|
||||
|
|
@ -332,7 +332,7 @@ object RemoverActor extends SupportActorCaseConversions {
|
|||
* @see `FirstStandardDuration`
|
||||
* @param obj the target
|
||||
* @param zone the zone in which this target is registered
|
||||
* @param duration how much longer the target will exist in its current state (in nanoseconds);
|
||||
* @param duration how much longer the target will exist in its current state (in milliseconds);
|
||||
* a default time duration is provided by implementation
|
||||
*/
|
||||
case class AddTask(obj: PlanetSideGameObject, zone: Zone, duration: Option[FiniteDuration] = None)
|
||||
|
|
|
|||
|
|
@ -456,6 +456,15 @@ class AvatarService(zone: Zone) extends Actor {
|
|||
)
|
||||
)
|
||||
|
||||
case AvatarAction.FacilityCaptureRewards(building_id, zone_number, exp) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(
|
||||
s"/$forChannel/Avatar",
|
||||
Service.defaultPlayerGUID,
|
||||
AvatarResponse.FacilityCaptureRewards(building_id, zone_number, exp)
|
||||
)
|
||||
)
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ object AvatarAction {
|
|||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Action
|
||||
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Action
|
||||
final case class AwardCep(charId: Long, bep: Long) extends Action
|
||||
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Action
|
||||
|
||||
final case class TeardownConnection() extends Action
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
|
|
|
|||
|
|
@ -129,4 +129,5 @@ object AvatarResponse {
|
|||
final case class UpdateKillsDeathsAssists(charId: Long, kda: KDAStat) extends Response
|
||||
final case class AwardBep(charId: Long, bep: Long, expType: ExperienceType) extends Response
|
||||
final case class AwardCep(charId: Long, bep: Long) extends Response
|
||||
final case class FacilityCaptureRewards(building_id: Int, zone_number: Int, exp: Long) extends Response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,7 @@ class DoorCloseActor() extends Actor {
|
|||
})
|
||||
|
||||
if (openDoors.nonEmpty) {
|
||||
val short_timeout: FiniteDuration =
|
||||
math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)) nanoseconds
|
||||
val short_timeout: FiniteDuration = math.max(1, DoorCloseActor.timeout_time - (now - openDoors.head.time)).milliseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
doorCloserTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, DoorCloseActor.TryCloseDoors())
|
||||
}
|
||||
|
|
@ -67,7 +66,7 @@ class DoorCloseActor() extends Actor {
|
|||
* and newer entries are always added to the end of the main `List`,
|
||||
* processing in order is always correct.
|
||||
* @param list the `List` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param now the time right now (in milliseconds)
|
||||
* @see `List.partition`
|
||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||
*/
|
||||
|
|
@ -84,7 +83,7 @@ class DoorCloseActor() extends Actor {
|
|||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* @param iter the `Iterator` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param now the time right now (in milliseconds)
|
||||
* @param index a persistent record of the index where list division should occur;
|
||||
* defaults to 0
|
||||
* @return the index where division will occur
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
|
|||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.duration.{FiniteDuration, _}
|
||||
import scala.util.Random
|
||||
|
||||
|
|
@ -38,7 +37,7 @@ class HackCaptureActor extends Actor {
|
|||
val duration = target.Definition.FacilityHackTime
|
||||
target.HackedBy match {
|
||||
case Some(hackInfo) =>
|
||||
target.HackedBy = hackInfo.Duration(duration.toNanos)
|
||||
target.HackedBy = hackInfo.Duration(duration.toMillis)
|
||||
case None =>
|
||||
log.error(s"Initial $target hack information is missing")
|
||||
}
|
||||
|
|
@ -57,8 +56,8 @@ class HackCaptureActor extends Actor {
|
|||
case HackCaptureActor.ProcessCompleteHacks() =>
|
||||
log.trace("Processing complete hacks")
|
||||
clearTrigger.cancel()
|
||||
val now: Long = System.nanoTime
|
||||
val (stillHacked, finishedHacks) = hackedObjects.partition(x => now - x.hack_timestamp < x.duration.toNanos)
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val (stillHacked, finishedHacks) = hackedObjects.partition(x => now - x.hack_timestamp < x.duration.toMillis)
|
||||
hackedObjects = stillHacked
|
||||
finishedHacks.foreach { entry =>
|
||||
val terminal = entry.target
|
||||
|
|
@ -213,9 +212,8 @@ class HackCaptureActor extends Actor {
|
|||
|
||||
private def RestartTimer(): Unit = {
|
||||
if (hackedObjects.nonEmpty) {
|
||||
val hackEntry = hackedObjects.reduceLeft(HackCaptureActor.minTimeLeft(System.nanoTime()))
|
||||
val short_timeout: FiniteDuration =
|
||||
math.max(1, hackEntry.duration.toNanos - (System.nanoTime - hackEntry.hack_timestamp)).nanoseconds
|
||||
val hackEntry = hackedObjects.reduceLeft(HackCaptureActor.minTimeLeft(System.currentTimeMillis()))
|
||||
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration.toMillis - (System.currentTimeMillis() - hackEntry.hack_timestamp)).milliseconds
|
||||
log.trace(s"RestartTimer: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds")
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
clearTrigger = context.system.scheduler.scheduleOnce(short_timeout, self, HackCaptureActor.ProcessCompleteHacks())
|
||||
|
|
@ -229,7 +227,7 @@ object HackCaptureActor {
|
|||
zone: Zone,
|
||||
unk1: Long,
|
||||
unk2: Long,
|
||||
startTime: Long = System.nanoTime()
|
||||
startTime: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
|
||||
|
|
@ -256,8 +254,7 @@ object HackCaptureActor {
|
|||
17039360L
|
||||
case Some(Hackable.HackInfo(p, _, start, length)) =>
|
||||
// See PlanetSideAttributeMessage #20 documentation for an explanation of how the timer is calculated
|
||||
val hackTimeRemainingMS =
|
||||
TimeUnit.MILLISECONDS.convert(math.max(0, start + length - System.nanoTime), TimeUnit.NANOSECONDS)
|
||||
val hackTimeRemainingMS = math.max(0, start + length - System.currentTimeMillis())
|
||||
val startNum = p.Faction match {
|
||||
case PlanetSideEmpire.TR => 0x10000
|
||||
case PlanetSideEmpire.NC => 0x20000
|
||||
|
|
@ -273,8 +270,8 @@ object HackCaptureActor {
|
|||
entry1: HackCaptureActor.HackEntry,
|
||||
entry2: HackCaptureActor.HackEntry
|
||||
): HackCaptureActor.HackEntry = {
|
||||
val entry1TimeLeft = entry1.duration.toNanos - (now - entry1.hack_timestamp)
|
||||
val entry2TimeLeft = entry2.duration.toNanos - (now - entry2.hack_timestamp)
|
||||
val entry1TimeLeft = entry1.duration.toMillis - (now - entry1.hack_timestamp)
|
||||
val entry2TimeLeft = entry2.duration.toMillis - (now - entry2.hack_timestamp)
|
||||
if (entry1TimeLeft < entry2TimeLeft) {
|
||||
entry1
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -29,15 +29,15 @@ class HackClearActor() extends Actor {
|
|||
|
||||
def receive: Receive = {
|
||||
case HackClearActor.ObjectIsHacked(target, zone, unk1, unk2, duration, time) =>
|
||||
val durationNanos = TimeUnit.NANOSECONDS.convert(duration, TimeUnit.SECONDS)
|
||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationNanos)
|
||||
val durationMillis = TimeUnit.MILLISECONDS.convert(duration, TimeUnit.SECONDS)
|
||||
hackedObjects = hackedObjects :+ HackClearActor.HackEntry(target, zone, unk1, unk2, time, durationMillis)
|
||||
|
||||
// Restart the timer, in case this is the first object in the hacked objects list
|
||||
RestartTimer()
|
||||
|
||||
case HackClearActor.TryClearHacks() =>
|
||||
clearTrigger.cancel()
|
||||
val now: Long = System.nanoTime
|
||||
val now: Long = System.currentTimeMillis()
|
||||
//TODO we can just walk across the list of doors and extract only the first few entries
|
||||
val (unhackObjects, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||
hackedObjects = stillHackedObjects
|
||||
|
|
@ -75,12 +75,12 @@ class HackClearActor() extends Actor {
|
|||
|
||||
private def RestartTimer(): Unit = {
|
||||
if (hackedObjects.nonEmpty) {
|
||||
val now = System.nanoTime()
|
||||
val now = System.currentTimeMillis()
|
||||
val (_/*unhackObjects*/, stillHackedObjects) = PartitionEntries(hackedObjects, now)
|
||||
|
||||
stillHackedObjects.headOption match {
|
||||
case Some(hackEntry) =>
|
||||
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration - (now - hackEntry.time)) nanoseconds
|
||||
val short_timeout: FiniteDuration = math.max(1, hackEntry.duration - (now - hackEntry.time)).milliseconds
|
||||
|
||||
log.debug(
|
||||
s"HackClearActor: still items left in hacked objects list. Checking again in ${short_timeout.toSeconds} seconds"
|
||||
|
|
@ -102,7 +102,7 @@ class HackClearActor() extends Actor {
|
|||
* and newer entries are always added to the end of the main `List`,
|
||||
* processing in order is always correct.
|
||||
* @param list the `List` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param now the time right now (in milliseconds)
|
||||
* @see `List.partition`
|
||||
* @return a `Tuple` of two `Lists`, whose qualifications are explained above
|
||||
*/
|
||||
|
|
@ -119,7 +119,7 @@ class HackClearActor() extends Actor {
|
|||
* a `List` of elements that have exceeded the time limit,
|
||||
* and a `List` of elements that still satisfy the time limit.
|
||||
* @param iter the `Iterator` of entries to divide
|
||||
* @param now the time right now (in nanoseconds)
|
||||
* @param now the time right now (in milliseconds)
|
||||
* @param index a persistent record of the index where list division should occur;
|
||||
* defaults to 0
|
||||
* @return the index where division will occur
|
||||
|
|
@ -158,7 +158,7 @@ object HackClearActor {
|
|||
unk1: Long,
|
||||
unk2: Long,
|
||||
duration: Int,
|
||||
time: Long = System.nanoTime()
|
||||
time: Long = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
@ -185,7 +185,7 @@ object HackClearActor {
|
|||
* @param target the server object
|
||||
* @param zone the zone in which the object resides
|
||||
* @param time when the object was hacked
|
||||
* @param duration The hack duration in nanoseconds
|
||||
* @param duration The hack duration in milliseconds
|
||||
* @see `ObjectIsHacked`
|
||||
*/
|
||||
private final case class HackEntry(
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ abstract class SupportActor[A <: SupportActor.Entry] extends Actor {
|
|||
|
||||
object SupportActor {
|
||||
class Entry(val obj: PlanetSideGameObject, val zone: Zone, val duration: Long) {
|
||||
val time: Long = System.nanoTime
|
||||
val time: Long = System.currentTimeMillis()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
|||
entryManagementBehaviors
|
||||
.orElse {
|
||||
case TurretUpgrader.AddTask(turret, zone, upgrade, duration) =>
|
||||
val lengthOfTime = duration.getOrElse(TurretUpgrader.StandardUpgradeLifetime).toNanos
|
||||
if (lengthOfTime > (1 second).toNanos) { //don't even bother if it's too short; it'll revert near instantly
|
||||
val lengthOfTime = duration.getOrElse(TurretUpgrader.StandardUpgradeLifetime).toMillis
|
||||
if (lengthOfTime > (1 second).toMillis) { //don't even bother if it's too short; it'll revert near instantly
|
||||
val entry = CreateEntry(turret, zone, TurretUpgrade.None, lengthOfTime)
|
||||
UpgradeTurretAmmo(CreateEntry(turret, zone, upgrade, lengthOfTime))
|
||||
if (list.isEmpty) {
|
||||
|
|
@ -73,7 +73,7 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
|||
|
||||
case TurretUpgrader.Downgrade() =>
|
||||
task.cancel()
|
||||
val now: Long = System.nanoTime
|
||||
val now: Long = System.currentTimeMillis()
|
||||
val (in, out) =
|
||||
list.partition(entry => {
|
||||
now - entry.time >= entry.duration
|
||||
|
|
@ -85,10 +85,10 @@ class TurretUpgrader extends SupportActor[TurretUpgrader.Entry] {
|
|||
case _ => ;
|
||||
}
|
||||
|
||||
def RetimeFirstTask(now: Long = System.nanoTime): Unit = {
|
||||
def RetimeFirstTask(now: Long = System.currentTimeMillis()): Unit = {
|
||||
task.cancel()
|
||||
if (list.nonEmpty) {
|
||||
val short_timeout: FiniteDuration = math.max(1, list.head.duration - (now - list.head.time)) nanoseconds
|
||||
val short_timeout: FiniteDuration = math.max(1, list.head.duration - (now - list.head.time)).milliseconds
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
task = context.system.scheduler.scheduleOnce(short_timeout, self, TurretUpgrader.Downgrade())
|
||||
}
|
||||
|
|
@ -249,7 +249,7 @@ object TurretUpgrader extends SupportActorCaseConversions {
|
|||
* @param _obj the target
|
||||
* @param _zone the zone in which this target is registered
|
||||
* @param upgrade the next upgrade state for this turret
|
||||
* @param _duration how much longer the target will exist in its current state (in nanoseconds)
|
||||
* @param _duration how much longer the target will exist in its current state (in milliseconds)
|
||||
*/
|
||||
case class Entry(_obj: PlanetSideGameObject, _zone: Zone, upgrade: TurretUpgrade.Value, _duration: Long)
|
||||
extends SupportActor.Entry(_obj, _zone, _duration)
|
||||
|
|
|
|||
|
|
@ -150,9 +150,6 @@ case class GameConfig(
|
|||
instantAction: InstantActionConfig,
|
||||
amenityAutorepairRate: Float,
|
||||
amenityAutorepairDrainRate: Float,
|
||||
bepRate: Double,
|
||||
cepRate: Double,
|
||||
maximumCepPerSquadSize: Seq[Int],
|
||||
newAvatar: NewAvatar,
|
||||
hart: HartConfig,
|
||||
sharedMaxCooldown: Boolean,
|
||||
|
|
@ -162,7 +159,8 @@ case class GameConfig(
|
|||
cavernRotation: CavernRotationConfig,
|
||||
savedMsg: SavedMessageEvents,
|
||||
playerDraw: PlayerStateDrawSettings,
|
||||
doorsCanBeOpenedByMedAppFromThisDistance: Float
|
||||
doorsCanBeOpenedByMedAppFromThisDistance: Float,
|
||||
experience: Experience
|
||||
)
|
||||
|
||||
case class InstantActionConfig(
|
||||
|
|
@ -238,3 +236,51 @@ case class PlayerStateDrawSettings(
|
|||
assert(ranges.nonEmpty)
|
||||
assert(ranges.size == delays.size)
|
||||
}
|
||||
|
||||
case class Experience(
|
||||
shortContributionTime: Long,
|
||||
longContributionTime: Long,
|
||||
bep: BattleExperiencePoints,
|
||||
sep: SupportExperiencePoints,
|
||||
cep: CommandExperiencePoints
|
||||
) {
|
||||
assert(shortContributionTime < longContributionTime)
|
||||
}
|
||||
|
||||
case class BattleExperiencePoints(
|
||||
base: BattleExperiencePointsBase,
|
||||
rate: Float
|
||||
)
|
||||
|
||||
case class BattleExperiencePointsBase(
|
||||
bopsMultiplier: Long,
|
||||
asMax: Long,
|
||||
withKills: Long,
|
||||
asMounted: Long,
|
||||
mature: Long
|
||||
)
|
||||
|
||||
case class SupportExperiencePoints(
|
||||
rate: Float,
|
||||
ntuSiloDepositReward: Long,
|
||||
canNotFindEventDefaultValue: Long,
|
||||
events: Seq[SupportExperienceEvent]
|
||||
)
|
||||
|
||||
case class SupportExperienceEvent(
|
||||
name: String,
|
||||
base: Long,
|
||||
shotsMultiplier: Float = 0f,
|
||||
shotsNatLog: Double = 0f,
|
||||
shotsLimit: Int = 50,
|
||||
shotsCutoff: Int = 50,
|
||||
amountMultiplier: Float = 0f
|
||||
)
|
||||
|
||||
case class CommandExperiencePoints(
|
||||
rate: Float,
|
||||
lluCarrierModifier: Float,
|
||||
maximumPerSquadSize: Seq[Int],
|
||||
squadSizeLimitOverflow: Int,
|
||||
squadSizeLimitOverflowMultiplier: Float
|
||||
)
|
||||
|
|
|
|||
|
|
@ -624,7 +624,7 @@ class PlayerControlDeathStandingTest extends ActorTest {
|
|||
// override def AvatarEvents = avatarProbe.ref
|
||||
// override def Activity = activityProbe.ref
|
||||
// }
|
||||
// zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
|
||||
// zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.currentTimeMillis()}")
|
||||
//
|
||||
// player1.Zone = zone
|
||||
// player1.Spawn()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue