You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

432 lines
12 KiB
Rust

use crate::plugin::*;
use crate::server_info::counters::*;
use crate::server_info::events::PlayerPosition;
use crate::server_info::Specialization::{Combat, Magic, Stealth};
use serde::Serialize;
use std::collections::{HashMap, HashSet};
use std::os::raw::{c_double, c_int, c_ushort};
#[derive(Serialize, Debug, Copy, Clone, Default, PartialOrd, PartialEq)]
pub struct Vec3 {
pub x: c_double,
pub y: c_double,
pub z: c_double,
}
impl Vec3 {
fn distance(&self, rhs: Vec3) -> f64 {
let x = self.x - rhs.x;
let y = self.y - rhs.y;
let z = self.z - rhs.z;
((x * x) + (y * y) + (z * z)).sqrt()
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u16)]
#[allow(dead_code)]
pub enum Attribute {
Strength = 0,
Intelligence = 1,
Willpower = 2,
Agility = 3,
Speed = 4,
Endurance = 5,
Personality = 6,
Luck = 7,
}
#[derive(Serialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct AttributeValue {
pub id: c_ushort,
pub name: String,
pub damage: c_double,
pub modifier: c_int,
pub base: c_int,
}
impl AttributeValue {
fn get(player_id: c_ushort, attribute_id: c_ushort) -> AttributeValue {
AttributeValue {
id: attribute_id,
name: get_attribute_name(attribute_id),
damage: get_attribute_damage(player_id, attribute_id),
modifier: get_attribute_modifier(player_id, attribute_id),
base: get_attribute_base(player_id, attribute_id),
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u16)]
#[allow(dead_code)]
pub enum Skill {
Block = 0,
Armorer = 1,
MediumArmor = 2,
HeavyArmor = 3,
Blunt = 4,
Longblade = 5,
Axe = 6,
Spear = 7,
Athletics = 8,
Enchant = 9,
Destruction = 10,
Alteration = 11,
Illusion = 12,
Conjuration = 13,
Mysticism = 14,
Restoration = 15,
Alchemy = 16,
Unarmored = 17,
Security = 18,
Sneak = 19,
Acrobatics = 20,
LightArmor = 21,
Shortblade = 22,
Marksman = 23,
Mercantile = 24,
Speechcraft = 25,
HandToHand = 26,
}
#[derive(Serialize, Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)]
pub enum SkillType {
Major,
Minor,
Misc,
}
impl Default for SkillType {
fn default() -> Self {
SkillType::Misc
}
}
#[derive(Serialize, Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
#[repr(i32)]
pub enum Specialization {
Combat = 0,
Magic = 1,
Stealth = 2,
None = 3,
}
impl Specialization {
fn get_for_skill(skill_id: u16) -> Specialization {
if skill_id < 9 {
Combat
} else if skill_id < 18 {
Magic
} else if skill_id < 27 {
Stealth
} else {
Specialization::None
}
}
fn get(id: c_int) -> Specialization {
match id {
0 => Specialization::Combat,
1 => Specialization::Magic,
2 => Specialization::Stealth,
_ => Specialization::None,
}
}
}
impl Default for Specialization {
fn default() -> Self {
Specialization::None
}
}
#[derive(Serialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct SkillValue {
pub id: c_ushort,
pub name: String,
pub progress: c_double,
pub base: c_int,
pub increase: c_int,
pub modifier: c_int,
pub damage: c_double,
pub progress_requirement: c_double,
pub progress_percent: c_double,
pub skill_type: SkillType,
}
impl SkillValue {
fn get(player_id: c_ushort, skill_id: c_ushort) -> Self {
SkillValue {
id: skill_id,
name: get_skill_name(skill_id),
progress: get_skill_progress(player_id, skill_id),
base: get_skill_base(player_id, skill_id),
increase: get_skill_increase(player_id, skill_id.into()),
modifier: get_skill_modifier(player_id, skill_id),
damage: get_skill_damage(player_id, skill_id),
progress_requirement: 0f64,
progress_percent: 0f64,
skill_type: SkillType::Minor,
}
}
fn calculate_progress(&mut self, is_specialization: bool, skill_type: SkillType) {
let mut requirement = (1 + self.base) as f64;
self.skill_type = skill_type;
requirement *= match skill_type {
SkillType::Major => 0.75,
SkillType::Minor => 1.0,
SkillType::Misc => 1.25,
};
if is_specialization {
requirement *= 0.8;
}
self.progress_requirement = requirement;
self.progress_percent = self.progress / requirement;
}
}
impl Into<(c_double, c_double, c_double)> for Vec3 {
fn into(self) -> (c_double, c_double, c_double) {
(self.x, self.y, self.z)
}
}
impl From<(c_double, c_double, c_double)> for Vec3 {
fn from(x: (c_double, c_double, c_double)) -> Self {
Vec3::new(x.0, x.1, x.2)
}
}
impl Vec3 {
pub fn new(x: c_double, y: c_double, z: c_double) -> Self {
Self { x, y, z }
}
pub fn get_position(player_id: c_ushort) -> Self {
Self::new(
get_pos_x(player_id),
get_pos_y(player_id),
get_pos_z(player_id),
)
}
pub fn get_rotation(player_id: c_ushort) -> Self {
Self::new(get_rot_x(player_id), 0.into(), get_rot_z(player_id))
}
}
#[derive(Serialize, Clone, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct Player {
pub id: c_ushort,
pub name: String,
pub head: String,
pub hair: String,
pub logged_in: bool,
pub distance_travelled: f64,
pub race: String,
pub class: PlayerClass,
pub cell: String,
pub is_outside: bool,
pub position: Vec3,
pub rotation: Vec3,
pub health: c_double,
pub health_base: c_double,
pub fatigue: c_double,
pub fatigue_base: c_double,
pub magicka: c_double,
pub magicka_base: c_double,
pub level: c_int,
pub level_progress: c_int,
pub attributes: Vec<AttributeValue>,
pub skills: Vec<SkillValue>,
pub major_skills: HashSet<c_ushort>,
pub minor_skills: HashSet<c_ushort>,
pub specialisation: Specialization,
}
#[derive(Serialize, Clone, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum PlayerClass {
Custom { name: String, description: String },
Default { name: String },
None,
}
impl Default for PlayerClass {
fn default() -> Self {
PlayerClass::None
}
}
impl Player {
pub fn new(id: c_ushort) -> Self {
let mut player = Player::default();
player.id = id;
player.update();
player.low_frequency_update();
player
}
pub fn get_player_position(&self) -> PlayerPosition {
PlayerPosition {
name: self.name.clone(),
position: (self.position.x, self.position.y),
rotation: self.rotation.z,
cell: self.cell.clone(),
is_outside: self.is_outside,
}
}
pub fn get_skill_type(&self, skill_id: c_ushort) -> SkillType {
if self.major_skills.contains(&skill_id) {
SkillType::Major
} else if self.minor_skills.contains(&skill_id) {
SkillType::Minor
} else {
SkillType::Misc
}
}
pub fn update(&mut self) {
self.rotation = Vec3::get_rotation(self.id);
self.is_outside = is_in_exterior(self.id);
let cell = get_cell(self.id);
let position = Vec3::get_position(self.id);
if cell == self.cell {
self.distance_travelled += self.position.distance(position)
}
self.position = position;
self.cell = cell;
self.health_base = get_health_base(self.id);
self.health = get_health_current(self.id);
self.fatigue_base = get_fatigue_base(self.id);
self.fatigue = get_fatigue_current(self.id);
self.magicka_base = get_magicka_base(self.id);
self.magicka = get_magicka_current(self.id);
self.level = get_level(self.id);
self.level_progress = get_level_progress(self.id)
}
pub fn update_once(&mut self) {
self.name = get_name(self.id);
self.race = get_race(self.id);
self.head = get_head(self.id);
self.hair = get_hair(self.id);
self.major_skills = HashSet::new();
self.major_skills
.insert(get_class_major_attribute(self.id, 0) as c_ushort);
self.major_skills
.insert(get_class_major_attribute(self.id, 1) as c_ushort);
self.minor_skills = HashSet::new();
self.minor_skills
.insert(get_class_minor_skill(self.id, 0) as c_ushort);
self.minor_skills
.insert(get_class_minor_skill(self.id, 1) as c_ushort);
self.minor_skills
.insert(get_class_minor_skill(self.id, 2) as c_ushort);
self.minor_skills
.insert(get_class_minor_skill(self.id, 3) as c_ushort);
self.minor_skills
.insert(get_class_minor_skill(self.id, 4) as c_ushort);
self.specialisation = Specialization::get(get_class_specialization(self.id));
let default_class = get_default_class(self.id);
if default_class.len() == 0 {
self.class = PlayerClass::Custom {
name: get_class_name(self.id),
description: get_class_desc(self.id),
}
} else {
self.class = PlayerClass::Default {
name: default_class,
}
}
}
pub fn low_frequency_update(&mut self) {
self.attributes = (0..get_attribute_count() as c_ushort)
.map(|id| AttributeValue::get(self.id, id))
.collect();
self.skills = (0..get_skill_count() as c_ushort)
.map(|id| {
let mut skill = SkillValue::get(self.id, id);
skill.calculate_progress(
self.specialisation == Specialization::get_for_skill(id),
self.get_skill_type(id),
);
skill
})
.collect();
if self.logged_in {
self.report_stats();
}
}
pub fn report_stats(&mut self) {
for skill in &self.skills {
let mut map = HashMap::new();
map.insert("player", self.name.as_str());
map.insert("skill", &skill.name.as_str());
let id = skill.id.to_string();
map.insert("skill_id", &id);
SKILL_LEVEL.with(&map).set(skill.base as i64);
SKILL_PROGRESS.with(&map).set(skill.progress_percent);
}
for attribute in &self.attributes {
let mut map = HashMap::new();
map.insert("player", self.name.as_str());
map.insert("attribute", &attribute.name.as_str());
let id = attribute.id.to_string();
map.insert("attribute_id", &id);
ATTRIBUTE_LEVEL.with(&map).set(attribute.base as i64);
}
let mut map = HashMap::new();
map.insert("player", self.name.as_str());
LEVEL.with(&map).set(self.level as i64);
LEVEL_PROGRESS.with(&map).set(self.level_progress as i64);
MAGICKA_BASE.with(&map).set(self.magicka_base);
MAGICKA.with(&map).set(self.magicka);
HEALTH_BASE.with(&map).set(self.health_base);
HEALTH.with(&map).set(self.health);
FATIGUE_BASE.with(&map).set(self.fatigue_base);
FATIGUE.with(&map).set(self.fatigue);
DISTANCE_TRAVELED
.with(&map)
.observe(self.distance_travelled);
self.distance_travelled = 0.0;
}
pub fn on_login(&mut self) {
send_message(self.id, "#ff0000This server runs #0000ffnwahttp#ff0000 and this is it's obnoxious login message for #00ff00you#ff0000!!\n", false, false);
self.update_once();
self.low_frequency_update();
}
}