scroll.server/crates/logic/src/data/logic_data.rs
WiseDev 07f0ee5427 converge combat timers, projectile references, pending damage, and the spawn ring
- replay the shooter's combat update a second time when it launches a
  projectile, matching the client's own component-pass re-entry
- carry the shooter's damage effect on every projectile and null a
  shot's target/source when either leaves the board
- derive pending physical damage from the shots actually in flight
  instead of an incremental total, so it can never go negative
- split the spawn ring's two angle registers so three- and five-unit
  cards land where the client puts them
- attribute a combat target left at NONE to the exact site that did it,
  gated to report each object once
- number and annotate every checksum field so a client-reported
  mismatch resolves straight to a name
2026-08-28 16:27:07 +03:00

111 lines
3.3 KiB
Rust

use crate::data::scaled::{level_scale, ScaleKind};
use std::sync::Arc;
use titan::{CsvRow, CsvTable, GlobalId};
#[derive(Debug, Clone)]
pub struct LogicData {
csv_table: Arc<CsvTable>,
row_index: usize,
global_id: GlobalId,
name: String,
}
impl LogicData {
pub fn new(csv_table: Arc<CsvTable>, row_index: usize, table_index: i32) -> Self {
let name = csv_table
.row_at(row_index)
.map(|row| row.string(0).to_owned())
.unwrap_or_default();
Self {
csv_table,
row_index,
global_id: GlobalId::new(table_index, row_index as i32),
name,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn global_id(&self) -> GlobalId {
self.global_id
}
pub fn table_index(&self) -> i32 {
self.global_id.class_id
}
pub fn instance_id(&self) -> i32 {
self.global_id.instance_id
}
pub fn row_index(&self) -> usize {
self.row_index
}
pub fn csv_table(&self) -> &Arc<CsvTable> {
&self.csv_table
}
pub fn row(&self) -> Option<&CsvRow> {
self.csv_table.row_at(self.row_index)
}
pub fn array_size(&self) -> usize {
self.row().map(CsvRow::array_size).unwrap_or_default()
}
fn column(&self, name: &str) -> Option<usize> {
self.csv_table.column_index(name)
}
pub fn int(&self, column: &str) -> i32 {
match (self.row(), self.column(column)) {
(Some(row), Some(index)) => row.int(index),
_ => 0,
}
}
pub fn int_at(&self, column: &str, level: usize) -> i32 {
match (self.row(), self.column(column)) {
(Some(row), Some(index)) => row.scaled_int(index, level),
_ => 0,
}
}
pub fn scaled_value(&self, column: &str, level: i32, kind: ScaleKind) -> i32 {
let (Some(row), Some(index)) = (self.row(), self.column(column)) else {
return 0;
};
let count = row.value_count(index);
if count == 0 {
return 0;
}
if count == 1 {
return row
.int(index)
.wrapping_mul(level_scale(kind.percent(), level))
/ 100;
}
let index_at = if count < 8 {
level.min(count as i32 - 1)
} else {
level
};
if index_at < 0 {
return 0;
}
row.int_at(index, index_at as usize)
}
pub fn string_at(&self, column: &str, index: usize) -> &str {
match (self.row(), self.column(column)) {
(Some(row), Some(column)) => row.string_at(column, index),
_ => "",
}
}
pub fn value_count(&self, column: &str) -> usize {
match (self.row(), self.column(column)) {
(Some(row), Some(column)) => row.value_count(column),
_ => 0,
}
}
pub fn string(&self, column: &str) -> &str {
match (self.row(), self.column(column)) {
(Some(row), Some(index)) => row.string(index),
_ => "",
}
}
pub fn boolean(&self, column: &str) -> bool {
match (self.row(), self.column(column)) {
(Some(row), Some(index)) => row.boolean(index),
_ => false,
}
}
}