# Block League docs

# Table of Contents
* [1. Game mechanics](#1-game-mechanics)
  * [1.1 Critical hits](#11-critical.hits)
  * [1.2 Immunity](#12-immunity)
  * [1.3 Medals](#13-medals)
  * [1.4 Achievements](#14-achievements)
  * [1.5 Experience system](#15-experience-system)
    * [1.5.1 Licences](#151-licences)
    * [1.5.2 Why levelling up](#152-why-levelling-up)
    * [1.5.3 Weekly playing time limit](#153-weekly-playing-time-limit)
* [2. Player metadata](#2-player-metadata)
* [3. Custom properties](#3-custom-properties)
  * [3.1 Weapon properties](#31-weapon-properties)
    * [3.1.1 Action properties](#311-action-properties)
  * [3.2 Entity properties](#32-entity-properties)
    * [3.2.1 Stand-alone weapons](#321-stand-alone-weapons)
    * [3.2.2 Bullets](#322-bullets)
* [4. API](#4-api)
  * [4.1 Callbacks](#41-callbacks)
* [5. Guidelines](#5-guidelines)
  * [5.1 Audio](#51-audio)
* [6. What to change after beta](#6-what-to-change-after-beta)
  

## 1. Game mechanics
### 1.1 Critical hits
Hitting players to their head with a raycast attack results in a critical hit (it can't be done with e.g. melee attacks for now, due to LT limitations).  

The formula is
```
critical_damage = damage * 1.5
```

### 1.2 Immunity
To avoid the practice of spawnkilling, players are immune for 3 seconds when they respawn. This is shown through a dark patina applied onto the character model + a text HUD sent to the player, displaying how many seconds until the immunity goes off.  

If a player shoots whilst being immune, immunity is removed before time.  
When a new round starts, nobody is immune. Meaning that if a player has died before the beginning of a new round and remained dead until then, it won't be immune when respawning.

### 1.3 Medals
TODO

### 1.4 Achievements
TODO (not really implemented yet)

### 1.5 Experience system
In Block League it's not possible to level up a character directly; weapons must be levelled up instead. There are 10 levels (or 3, during the beta) for each weapon and the sum of their levels return the level of a character. It's not necessary to play until the end of a match to earn experience points; also, it's possible to turn off experience for some weapons.
To level up, weapons require a certain amount of experience and a licence (more about licences in the following subsection).

The exp formula is the following and it applies to each equipped weapon
```
((w_damage_dealt / 50) + w_kills + (minutes_inside * 1.5) + outcome) * until_the_end * 4_or_more_players + * w_xp_multiplier
```
Where:
* `w_kills` is an integer. It represents the amount of kills made with that weapon. Assists and entity kills (e.g. sentry guns) are not taken into account.
* `outcome` is an integer. `0` in case of defeat, `5` in case of tie and `10` in case of victory
* `until_the_end` is a multiplier. If a player played until the end, its value is `1`; if they left whilst the match was still in progress, it's `0.75`
  * Rationale: the game shouldn't force people to stay until the end if they really have to leave, putting them in a situation of all or nothing (e.g. rage, dinner is ready)
* `4_or_more_players` is a multiplier. If the average amount of players is lesser than 4, it's `0.5`. Otherwise, it's `1`
  * Rationale: it makes farming harder, as it requires at least 4 clients open
* `w_xp_multiplier` is a float. With some weapons is way harder to get xp points, so this aims to balance the final result
  
However, the earned experience will be none in case of at least one of these conditions are met:
* the player spent less than 30 seconds inside
* the player didn't score at least 4 points

Excluding the multipliers, the average weapon exp earned per match is estimated to be 57 points.  
Weapons start from level 0 and, by default, the required exp to level up is `500` for lv.1 and `1000` for all the other levels. Therefore, every weapon requires a total of 9500 experience points (or 2500, during the beta) to be maxed. Considering that players can bring up to 3 weapons in each match, the estimated time to max three weapons is ~31h. However, Block League doesn't want to be about farming, and this is where licences come into play (extending the estimated time according to the player skills).

#### 1.5.1 Licences
Licences are per-weapon quests that players must complete in a single match in order to level up. They're unlocked once the experience bar is filled and the higher the level, the harder they'll be to get.  
Weapons stop earning experience until a licence is obtained, as the goal of licences is to reflect a player level that is bound more to their skills rather than their time spent playing: unskilled players won't be able to reach the highest levels, pushing them to work on their weapon dexterity and strategies in order to become stronger - and advance to the next level.

A licence won't be considered completed if:
* the goal has not been met
* players didn't play until the end
* the average amount of players is lesser than 4

#### 1.5.2 Why levelling up
Other than the sense of progress that levels create (in this case linked to the skills of a player), levels can open up to features like:
* better team balancing when launching a game
* game mechanics unlocked only after a certain level (e.g. ranked games, clans)

#### 1.5.3 Weekly playing time limit
Earning experience without brakes can result in an unhealthy experience for people whom struggle to stop playing. In order to avoid heavy grinding, Block League features a weekly playing time limit; which, if exceeded, prevents players from earning more experience until the week is over. The weekly limit resets every Thursday at midnight (00:00).  

The limit is by default 14 hours, tailored so as not to impact common players from feeling chained (they will never notice), but at the same time to stop heavy grinders from playing too much just to fill bars. The limit is weekly, as the game doesn't want to punish occasional daily events e.g. gaming nights and the like. Moreover, only the clock-ticking in-game time is counted, meaning idling in a server, loading and celebration phases are not taken into account. Tutorials are an exception as well.  


## 2. Player metadata
As a general rule, every player metadata is set to `0` when entering the arena.

* `bl_immunity`: (int) whether the player can be damaged by other players
  * set to `0` after a few seconds after respawning. Shooting and taking the ball instantly set it to 0
  * set to `1` when respawning and when the celebration phase starts
* `bl_invincibility`: (int) whether the player can be damaged in general (i.e. rays). It's an extra layer of safety to prevent damage when respawning
  * set to `0` one step after players respawn and rounds start
  * set to `1` when dying and when the arena loads


## 3. Custom properties
Block League weapons, actions and entities use specific fields, which are all `nil` by default

### 3.1 Weapon properties

* `_profile_description`: (string) the description that appears when selecting the weapon in the Block League profile
* `_hint`: (string) instructions regarding how to unlock the weapon/variant. Default is `nil`
* `_variant_name`: (string) the name of the variant, if any
* `_variant_of`: (string) the technical name of the base weapon, if any
* `_variants`: (table) in base weapons, a list of strings containing the technical names of all the variants registered for that weapon
* `_xp_multiplier`: (float) multiplies the xp gained by the weapon
* `_staff_only`: (boolean) whether it should only be available to staff (e.g. normal users can't see it in the profile). Default is `nil`
  * Always declare the staff-only weapons/variants last

#### 3.1.1 Action properties

* `_description`: (string) describes the action when selecting the weapon in the Block League profile
* `_friendly_fire`: (boolean) whether it should hurt your team (including yourself) as well
* `_friendly_dmg_perc`: (float) how much damage should deal in percentage, in case `_friendly_fire` is `true`. Range `0-1`

### 3.2 Entity properties
Shared:

* `_teamID`: (int) the ID of the team the entity belongs to

#### 3.2.1 Stand-alone weapons
(As for now, sentry guns)

* `_name`: (string) the name of the entity. Used to display the entity in the kill log
* `_loggable`: (boolean) whether the entity, once destroyed, shall appear in the kill log
* `_owner`: (string) who owns the entity, e.g. sentry guns
* `_can_be_targeted`: (boolean) whether the entity can be hit by stand-alone weapons such as sentries
* `_no_knockback`: (boolean) whether the weapon is subject to weapons' knocback. It requires `_can_be_targeted`
* `_dmg_received`: (table) tracks the damage dealt by every player and targettable entity, same as in the arena player property with the same name. Format `[p_name/p_name@ent._name] = {dmg, timestamp}`
* `_kill`: (function(self, killer)) custom actions to run when the entity is killed
* `_is_dying`: (boolean) whether the entity is dying. Used by `_kill`, hp are set to 1 in meanwhile

Each entity can then feature its unique fields

* Sentry guns: `_is_spawning`, `_arena`

#### 3.2.2 Bullets

Unique fields

* Bombs: `_collided`, `_ignited`


## 4. API
CORE
* `block_league.equip(p_name, w_name, slot)`: equips the given weapon (if unlocked) in the specified slot
* `block_league.unequip(p_name, w_name)`: unequips the given weapon
* `block_league.unlock_weapon(p_name, w_name)`: unlocks a weapon for the specified player
* `block_league.remove_weapon(p_name, w_name)`: removes a weapon for the specified player
* `block_league.unlock_weapon_variant(p_name, w_name, var_name)`: unlocks a variant of a weapon for the specified player. It requires the original weapon first
* `block_league.remove_weapon_variant(p_name, w_name, var_name)`: removes a variant of a weapon for the specified player

GETTERS
* `block_league.get_original_weapon(w_name)`: if `w_name` is a variant, it returns the technical name of the original weapon. Otherwise, `w_name`
* `block_league.get_unlocked_weapons(p_name)`: returns a list containing the technical names of all the weapons `p_name` has unlocked
* `block_league.get_unlocked_weapon_variants(p_name, w_name)`: returns a table containing all the variants of `w_name` that `p_name` has unlocked
* `block_league.get_equipped_weapons(p_name, <originals_only>)`: returns a list containing the technical names of the weapons `p_name` has currently equipped. If `originals_only` is `true`, every variant will be replaced with the name of the original weapon
* `block_league.get_equipped_skill(p_name)`: returns the technical name of the skill `p_name` has currently equipped
* `block_league.get_weapon_lv(p_name, w_name)`: returns the level of `w_name`, if unlocked
* `block_league.get_weapons_max_lv()`: returns the maximum level weapons can reach
* `block_league.get_xp(p_name, w_name)`: returns the amount of exp `p_name` currently has on `w_name`
* `block_league.get_xp_stored(p_name, w_name)`: returns the amount of exp that `p_name` will receive on `w_name` once the weapon levels up
* `block_league.get_xp_required_for_lv_up(p_name, w_name)`: returns how much exp is needed to fill up the exp bar
* `block_league.get_weapons_supporting_xp()`: returns a table containing the names of all the (original) weapons able to earn experience. Format `{w_name = true}`
* `block_league.get_player_level(p_name)`: returns the character level of the specified player
* `block_league.get_licence_description(w_name, lv)`: returns the goal to reach level `lv` of `w_name`
* `block_league.get_licence_info(w_name, lv)`: returns a table containing information about the specified licence.
  * `type`: (string) the type of licence. Supported values: `"KILL"`
  * `action`: (string) whether it requires a specific action. Supported values: `"ANY"`
  * `goal`: (integer) the goal to reach
  * `desc`: (string) the description of the licence, explaining what to do
  * `reward`: (table) if present, it contains the information regarding what should be unlocked when obtaining the licence.
    * `type`: (string) the type of reward. Supported values: `"SKIN"`
    * `item`: (string only?) the actual reward

SETTERS
* `block_league.set_xp(p_name, w_name, amount)`: sets the experience of `w_name` to the specified `amount`.
  * if `amount` is higher than the xp required to completely fill the bar, only the missing xp will be added (and the rest discarded)
* `block_league.set_level(p_name, w_name, w_lv)`: sets the level of `w_name` to the specified `w_lv`
  * Values lower than 0 and greater than the max level are not accepted

UTILS
* `block_league.has_weapon_equipped(p_name, w_name, <check_for_variants>)`: whether `p_name` has `w_name` currently equipped. if `check_for_variants` is `true`, it'll check whether the base weapon is the same
* `block_league.has_variant(p_name, w_name, var_name)`: whether `p_name` has unlocked `var_name`
* `block_league.is_weapon_exp_enabled(w_name)`: whether `w_name` is listed in the weapons able to earn exp
* `block_league.has_licence_in_progress(p_name)`: whether `p_name` has a licence in progress (it must be inside a match)
* `block_league.can_get_licence(p_name, w_name)`: whether `p_name` can unlock the licence of the specified weapon. It returns `true` if the exp bar is filled and the weapon is not maxed
* `block_league.has_exceeded_weekly_time(p_name)`: whether `p_name` has played more than the weekly playing time limit
* `block_league.add_xp(p_name, w_name, amount)`: adds `amount` to the specified weapon of `p_name`.
  * `amount` is always floored
  * if `amount` is higher than the xp required to completely fill the bar, only the missing xp will be added (and the rest stored for when the licence is completed)
* `block_league.flush_xp(p_name)`: resets player's level and xp. It requires debug mode to be enabled

GUI
* `block_league.show_profile(p_name)`: shows `p_name` their Block League profile
* `block_league.gui_show_guide_lvup(p_name)`: shows `p_name` the interface explaining how levelling up works

### 4.1 Callbacks
* `block_league.register_on_levelup(p_name, w_name, w_lv, p_lv)`: run right after a weapons has levelled up


## 5. Guidelines
### 5.1 Audio
Block League uses 4 different functions to play audio files to a single player, each one with its specific usecase
* `block_league.play_sound_to_player(p_name, sound)`: plays the sound to `p_name` and to whomever is attached onto them - both spectators and dead players
* `weapons_lib.play_sound(sound, p_name)`: like `play_sound_to_player`, but to keep weapons_lib convention is should be used only in weapons custom actions (e.g. Propulsor)
* `arena_lib.sound_play(p_name, sound, <override_params>)`: plays the sound to `p_name` and their spectators. It skips dead players
* `audio_lib.play_sound(sound, {to_player = p_name})`: ignores whatever spectator and dead player `p_name` might have (used for the tutorial)

## 6. What to change after beta
Block League is currently in beta. If you're using it, it's highly recommended to get rid of the player database once it ends, as aspects such as the xp formula might change. Other than that, here's what to expect when the beta is over:
* Silver weapons unlocked at lv. 2 instead of lv. 3
