diff --git a/lib/common/.gitignore b/lib/common/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/lib/common/.gitignore @@ -0,0 +1 @@ +/target diff --git a/lib/common/Cargo.lock b/lib/common/Cargo.lock new file mode 100644 index 0000000..4e65dd8 --- /dev/null +++ b/lib/common/Cargo.lock @@ -0,0 +1,121 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bnum" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50202def95bf36cb7d1d7a7962cea1c36a3f8ad42425e5d2b71d7acb8041b5b8" + +[[package]] +name = "fixed_wide" +version = "0.1.1" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "d9c2cf115b3785ede870fada07e8b1aeba3378345b4ca86fe3c772ecabc05c0f" +dependencies = [ + "arrayvec", + "bnum", + "paste", + "ratio_ops", +] + +[[package]] +name = "glam" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a" + +[[package]] +name = "id" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "2337e7a6c273082b672e377e159d7a168fb51438461b7c4033c79a515dd7a25a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "linear_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "b2e6977ac24f47086d8a7a2d4ae1c720e86dfdc8407cf5e34c18bfa01053c456" +dependencies = [ + "fixed_wide", + "paste", + "ratio_ops", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratio_ops" +version = "0.1.0" +source = "sparse+https://git.itzana.me/api/packages/strafesnet/cargo/" +checksum = "01239195d6afe0509e7e3511b716c0540251dfe7ece0a9a5a27116afb766c42c" + +[[package]] +name = "strafesnet_common" +version = "0.5.2" +dependencies = [ + "arrayvec", + "bitflags", + "fixed_wide", + "glam", + "id", + "linear_ops", + "ratio_ops", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" diff --git a/lib/common/Cargo.toml b/lib/common/Cargo.toml new file mode 100644 index 0000000..3729001 --- /dev/null +++ b/lib/common/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "strafesnet_common" +version = "0.5.2" +edition = "2021" +repository = "https://git.itzana.me/StrafesNET/common" +license = "MIT OR Apache-2.0" +description = "Common types and helpers for Strafe Client associated projects." +authors = ["Rhys Lloyd "] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +arrayvec = "0.7.4" +bitflags = "2.6.0" +fixed_wide = { version = "0.1.1", registry = "strafesnet", features = ["deferred-division","zeroes","wide-mul"] } +linear_ops = { version = "0.1.0", registry = "strafesnet", features = ["deferred-division","named-fields"] } +ratio_ops = { version = "0.1.0", registry = "strafesnet" } +glam = "0.29.0" +id = { version = "0.1.0", registry = "strafesnet" } diff --git a/lib/common/LICENSE-APACHE b/lib/common/LICENSE-APACHE new file mode 100644 index 0000000..a7e77cb --- /dev/null +++ b/lib/common/LICENSE-APACHE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/lib/common/LICENSE-MIT b/lib/common/LICENSE-MIT new file mode 100644 index 0000000..468cd79 --- /dev/null +++ b/lib/common/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/common/README.md b/lib/common/README.md new file mode 100644 index 0000000..6823d6c --- /dev/null +++ b/lib/common/README.md @@ -0,0 +1,19 @@ +StrafesNET Common Library +========================= + +## Common types used in the StrafesNET ecosystem + +#### License + + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + + +
+ + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + \ No newline at end of file diff --git a/lib/common/src/aabb.rs b/lib/common/src/aabb.rs new file mode 100644 index 0000000..5e244d2 --- /dev/null +++ b/lib/common/src/aabb.rs @@ -0,0 +1,56 @@ +use crate::integer::{vec3,Planar64Vec3}; + +#[derive(Clone)] +pub struct Aabb{ + min:Planar64Vec3, + max:Planar64Vec3, +} + +impl Default for Aabb{ + fn default()->Self{ + Self{min:vec3::MAX,max:vec3::MIN} + } +} + +impl Aabb{ + pub const fn new(min:Planar64Vec3,max:Planar64Vec3)->Self{ + Self{min,max} + } + pub const fn max(&self)->Planar64Vec3{ + self.max + } + pub const fn min(&self)->Planar64Vec3{ + self.min + } + pub fn grow(&mut self,point:Planar64Vec3){ + self.min=self.min.min(point); + self.max=self.max.max(point); + } + pub fn join(&mut self,aabb:&Aabb){ + self.min=self.min.min(aabb.min); + self.max=self.max.max(aabb.max); + } + pub fn inflate(&mut self,hs:Planar64Vec3){ + self.min-=hs; + self.max+=hs; + } + pub fn intersects(&self,aabb:&Aabb)->bool{ + let bvec=self.min.lt(aabb.max)&aabb.min.lt(self.max); + bvec.all() + } + pub fn size(&self)->Planar64Vec3{ + self.max-self.min + } + pub fn center(&self)->Planar64Vec3{ + self.min+(self.max-self.min)>>1 + } + //probably use floats for area & volume because we don't care about precision + // pub fn area_weight(&self)->f32{ + // let d=self.max-self.min; + // d.x*d.y+d.y*d.z+d.z*d.x + // } + // pub fn volume(&self)->f32{ + // let d=self.max-self.min; + // d.x*d.y*d.z + // } +} diff --git a/lib/common/src/bvh.rs b/lib/common/src/bvh.rs new file mode 100644 index 0000000..66d0b2e --- /dev/null +++ b/lib/common/src/bvh.rs @@ -0,0 +1,194 @@ +use crate::aabb::Aabb; + +//da algaritum +//lista boxens +//sort by {minx,maxx,miny,maxy,minz,maxz} (6 lists) +//find the sets that minimizes the sum of surface areas +//splitting is done when the minimum split sum of surface areas is larger than the node's own surface area + +//start with bisection into octrees because a bad bvh is still 1000x better than no bvh +//sort the centerpoints on each axis (3 lists) +//bv is put into octant based on whether it is upper or lower in each list + +pub enum RecursiveContent{ + Branch(Vec), + Leaf(T), +} +impl Default for RecursiveContent{ + fn default()->Self{ + Self::Branch(Vec::new()) + } +} +pub struct BvhNode{ + content:RecursiveContent,T>, + aabb:Aabb, +} +impl Default for BvhNode{ + fn default()->Self{ + Self{ + content:Default::default(), + aabb:Aabb::default(), + } + } +} +pub struct BvhWeightNode{ + content:RecursiveContent,T>, + weight:W, + aabb:Aabb, +} + +impl BvhNode{ + pub fn the_tester(&self,aabb:&Aabb,f:&mut F){ + match &self.content{ + RecursiveContent::Leaf(model)=>f(model), + RecursiveContent::Branch(children)=>for child in children{ + //this test could be moved outside the match statement + //but that would test the root node aabb + //you're probably not going to spend a lot of time outside the map, + //so the test is extra work for nothing + if aabb.intersects(&child.aabb){ + child.the_tester(aabb,f); + } + }, + } + } + pub fn into_visitor(self,f:&mut F){ + match self.content{ + RecursiveContent::Leaf(model)=>f(model), + RecursiveContent::Branch(children)=>for child in children{ + child.into_visitor(f) + }, + } + } + pub fn weigh_contents,F:Fn(&T)->W>(self,f:&F)->BvhWeightNode{ + match self.content{ + RecursiveContent::Leaf(model)=>BvhWeightNode{ + weight:f(&model), + content:RecursiveContent::Leaf(model), + aabb:self.aabb, + }, + RecursiveContent::Branch(children)=>{ + let branch:Vec>=children.into_iter().map(|child| + child.weigh_contents(f) + ).collect(); + BvhWeightNode{ + weight:branch.iter().map(|node|node.weight).sum(), + content:RecursiveContent::Branch(branch), + aabb:self.aabb, + } + }, + } + } +} + +impl BvhWeightNode{ + pub const fn weight(&self)->&W{ + &self.weight + } + pub const fn aabb(&self)->&Aabb{ + &self.aabb + } + pub fn into_content(self)->RecursiveContent,T>{ + self.content + } + pub fn into_visitor(self,f:&mut F){ + match self.content{ + RecursiveContent::Leaf(model)=>f(model), + RecursiveContent::Branch(children)=>for child in children{ + child.into_visitor(f) + }, + } + } +} + +pub fn generate_bvh(boxen:Vec<(T,Aabb)>)->BvhNode{ + generate_bvh_node(boxen,false) +} + +fn generate_bvh_node(boxen:Vec<(T,Aabb)>,force:bool)->BvhNode{ + let n=boxen.len(); + if force||n<20{ + let mut aabb=Aabb::default(); + let nodes=boxen.into_iter().map(|b|{ + aabb.join(&b.1); + BvhNode{ + content:RecursiveContent::Leaf(b.0), + aabb:b.1, + } + }).collect(); + BvhNode{ + content:RecursiveContent::Branch(nodes), + aabb, + } + }else{ + let mut sort_x=Vec::with_capacity(n); + let mut sort_y=Vec::with_capacity(n); + let mut sort_z=Vec::with_capacity(n); + for (i,(_,aabb)) in boxen.iter().enumerate(){ + let center=aabb.center(); + sort_x.push((i,center.x)); + sort_y.push((i,center.y)); + sort_z.push((i,center.z)); + } + sort_x.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); + sort_y.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); + sort_z.sort_by(|tup0,tup1|tup0.1.cmp(&tup1.1)); + let h=n/2; + let median_x=sort_x[h].1; + let median_y=sort_y[h].1; + let median_z=sort_z[h].1; + //locate a run of values equal to the median + //partition point gives the first index for which the predicate evaluates to false + let first_index_eq_median_x=sort_x.partition_point(|&(_,x)|xSelf{ + Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight) + } + pub const fn wasdqe()->Self{ + Self::MoveForward.union(Self::MoveLeft).union(Self::MoveBackward).union(Self::MoveRight).union(Self::MoveUp).union(Self::MoveDown) + } +} \ No newline at end of file diff --git a/lib/common/src/gameplay_attributes.rs b/lib/common/src/gameplay_attributes.rs new file mode 100644 index 0000000..ff01731 --- /dev/null +++ b/lib/common/src/gameplay_attributes.rs @@ -0,0 +1,174 @@ +use crate::model; +use crate::integer::{Time,Planar64,Planar64Vec3}; + +//you have this effect while in contact +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct ContactingLadder{ + pub sticky:bool +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum ContactingBehaviour{ + Surf, + Ladder(ContactingLadder), + NoJump, + Cling,//usable as a zipline, or other weird and wonderful things + Elastic(u32),//[1/2^32,1] 0=None (elasticity+1)/2^32 +} +//you have this effect while intersecting +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct IntersectingWater{ + pub viscosity:Planar64, + pub density:Planar64, + pub velocity:Planar64Vec3, +} +//All models can be given these attributes +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct Accelerator{ + pub acceleration:Planar64Vec3 +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum Booster{ + //Affine(crate::integer::Planar64Affine3),//capable of SetVelocity,DotVelocity,normal booster,bouncy part,redirect velocity, and much more + Velocity(Planar64Vec3),//straight up boost velocity adds to your current velocity + Energy{direction:Planar64Vec3,energy:Planar64},//increase energy in direction + AirTime(Time),//increase airtime, invariant across mass and gravity changes + Height(Planar64),//increase height, invariant across mass and gravity changes +} +impl Booster{ + pub fn boost(&self,velocity:Planar64Vec3)->Planar64Vec3{ + match self{ + &Booster::Velocity(boost_velocity)=>velocity+boost_velocity, + &Booster::Energy{..}=>{ + todo!() + //let d=direction.dot(velocity); + //TODO: think about negative + //velocity+direction.with_length((d*d+energy).sqrt()-d) + }, + Booster::AirTime(_)=>todo!(), + Booster::Height(_)=>todo!(), + } + } +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum TrajectoryChoice{ + HighArcLongDuration,//underhand lob at target: less horizontal speed and more air time + LowArcShortDuration,//overhand throw at target: more horizontal speed and less air time +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum SetTrajectory{ + //Speed-type SetTrajectory + AirTime(Time),//air time (relative to gravity direction) is invariant across mass and gravity changes + Height(Planar64),//boost height (relative to gravity direction) is invariant across mass and gravity changes + DotVelocity{direction:Planar64Vec3,dot:Planar64},//set your velocity in a specific direction without touching other directions + //Velocity-type SetTrajectory + TargetPointTime{//launch on a trajectory that will land at a target point in a set amount of time + target_point:Planar64Vec3, + time:Time,//short time = fast and direct, long time = launch high in the air, negative time = wrong way + }, + TargetPointSpeed{//launch at a fixed speed and land at a target point + target_point:Planar64Vec3, + speed:Planar64,//if speed is too low this will fail to reach the target. The closest-passing trajectory will be chosen instead + trajectory_choice:TrajectoryChoice, + }, + Velocity(Planar64Vec3),//SetVelocity +} +impl SetTrajectory{ + pub const fn is_absolute(&self)->bool{ + match self{ + SetTrajectory::AirTime(_) + |SetTrajectory::Height(_) + |SetTrajectory::DotVelocity{direction:_,dot:_}=>false, + SetTrajectory::TargetPointTime{target_point:_,time:_} + |SetTrajectory::TargetPointSpeed{target_point:_,speed:_,trajectory_choice:_} + |SetTrajectory::Velocity(_)=>true, + } + } +} +// enum TrapCondition{ +// FasterThan(Planar64), +// SlowerThan(Planar64), +// InRange(Planar64,Planar64), +// OutsideRange(Planar64,Planar64), +// } +#[derive(Clone,Hash,Eq,PartialEq)] +pub struct Wormhole{ + //destination does not need to be another wormhole + //this defines a one way portal to a destination model transform + //two of these can create a two way wormhole + pub destination_model:model::ModelId, + //(position,angles)*=origin.transform.inverse()*destination.transform +} +//attributes listed in order of handling +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct GeneralAttributes{ + pub booster:Option, + pub trajectory:Option, + pub wormhole:Option, + pub accelerator:Option, +} +impl GeneralAttributes{ + pub const fn any(&self)->bool{ + self.booster.is_some() + ||self.trajectory.is_some() + ||self.wormhole.is_some() + ||self.accelerator.is_some() + } + pub fn is_wrcp(&self)->bool{ + self.trajectory.as_ref().map_or(false,|t|t.is_absolute()) + /* + &&match &self.teleport_behaviour{ + Some(TeleportBehaviour::StageElement( + StageElement{ + mode_id, + stage_id:_, + force:true, + behaviour:StageElementBehaviour::Trigger|StageElementBehaviour::Teleport + } + ))=>current_mode_id==*mode_id, + _=>false, + } + */ + } +} +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct ContactingAttributes{ + //friction? + pub contact_behaviour:Option, +} +impl ContactingAttributes{ + pub const fn any(&self)->bool{ + self.contact_behaviour.is_some() + } +} +#[derive(Default,Clone,Hash,Eq,PartialEq)] +pub struct IntersectingAttributes{ + pub water:Option, +} +impl IntersectingAttributes{ + pub const fn any(&self)->bool{ + self.water.is_some() + } +} +#[derive(Clone,Copy,id::Id,Hash,Eq,PartialEq)] +pub struct CollisionAttributesId(u32); +#[derive(Clone,Default,Hash,Eq,PartialEq)] +pub struct ContactAttributes{ + pub contacting:ContactingAttributes, + pub general:GeneralAttributes, +} +#[derive(Clone,Default,Hash,Eq,PartialEq)] +pub struct IntersectAttributes{ + pub intersecting:IntersectingAttributes, + pub general:GeneralAttributes, +} +#[derive(Clone,Hash,Eq,PartialEq)] +pub enum CollisionAttributes{ + Decoration,//visual only + Contact(ContactAttributes),//track whether you are contacting the object + Intersect(IntersectAttributes),//track whether you are intersecting the object +} +impl CollisionAttributes{ + pub fn contact_default()->Self{ + Self::Contact(ContactAttributes::default()) + } +} diff --git a/lib/common/src/gameplay_modes.rs b/lib/common/src/gameplay_modes.rs new file mode 100644 index 0000000..b35acc6 --- /dev/null +++ b/lib/common/src/gameplay_modes.rs @@ -0,0 +1,331 @@ +use std::collections::{HashSet,HashMap}; +use crate::model::ModelId; +use crate::gameplay_style; +use crate::updatable::Updatable; + +#[derive(Clone)] +pub struct StageElement{ + stage_id:StageId,//which stage spawn to send to + force:bool,//allow setting to lower spawn id i.e. 7->3 + behaviour:StageElementBehaviour, + jump_limit:Option, +} +impl StageElement{ + #[inline] + pub const fn new(stage_id:StageId,force:bool,behaviour:StageElementBehaviour,jump_limit:Option)->Self{ + Self{ + stage_id, + force, + behaviour, + jump_limit, + } + } + #[inline] + pub const fn stage_id(&self)->StageId{ + self.stage_id + } + #[inline] + pub const fn force(&self)->bool{ + self.force + } + #[inline] + pub const fn behaviour(&self)->StageElementBehaviour{ + self.behaviour + } + #[inline] + pub const fn jump_limit(&self)->Option{ + self.jump_limit + } +} + +#[derive(Clone,Copy,Hash,Eq,PartialEq)] +pub enum StageElementBehaviour{ + SpawnAt,//must be standing on top to get effect. except cancollide false + Trigger, + Teleport, + Platform, + //Check(point) acts like a trigger if you haven't hit all the checkpoints on previous stages yet. + //Note that all stage elements act like this, this is just the isolated behaviour. + Check, + Checkpoint,//this is a combined behaviour for Ordered & Unordered in case a model is used multiple times or for both. +} + +#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq)] +pub struct CheckpointId(u32); +impl CheckpointId{ + pub const FIRST:Self=Self(0); +} +#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] +pub struct StageId(u32); +impl StageId{ + pub const FIRST:Self=Self(0); +} +#[derive(Clone)] +pub struct Stage{ + spawn:ModelId, + //open world support lol + ordered_checkpoints_count:u32, + unordered_checkpoints_count:u32, + //currently loaded checkpoint models + ordered_checkpoints:HashMap, + unordered_checkpoints:HashSet, +} +impl Stage{ + pub fn new( + spawn:ModelId, + ordered_checkpoints_count:u32, + unordered_checkpoints_count:u32, + ordered_checkpoints:HashMap, + unordered_checkpoints:HashSet, + )->Self{ + Self{ + spawn, + ordered_checkpoints_count, + unordered_checkpoints_count, + ordered_checkpoints, + unordered_checkpoints, + } + } + pub fn empty(spawn:ModelId)->Self{ + Self{ + spawn, + ordered_checkpoints_count:0, + unordered_checkpoints_count:0, + ordered_checkpoints:HashMap::new(), + unordered_checkpoints:HashSet::new(), + } + } + #[inline] + pub const fn spawn(&self)->ModelId{ + self.spawn + } + #[inline] + pub const fn ordered_checkpoints_count(&self)->u32{ + self.ordered_checkpoints_count + } + #[inline] + pub const fn unordered_checkpoints_count(&self)->u32{ + self.unordered_checkpoints_count + } + pub fn into_inner(self)->(HashMap,HashSet){ + (self.ordered_checkpoints,self.unordered_checkpoints) + } + #[inline] + pub const fn is_empty(&self)->bool{ + self.is_complete(0,0) + } + #[inline] + pub const fn is_complete(&self,ordered_checkpoints_count:u32,unordered_checkpoints_count:u32)->bool{ + self.ordered_checkpoints_count==ordered_checkpoints_count&&self.unordered_checkpoints_count==unordered_checkpoints_count + } + #[inline] + pub fn is_next_ordered_checkpoint(&self,next_ordered_checkpoint_id:CheckpointId,model_id:ModelId)->bool{ + self.ordered_checkpoints.get(&next_ordered_checkpoint_id).is_some_and(|&next_checkpoint|model_id==next_checkpoint) + } + #[inline] + pub fn is_unordered_checkpoint(&self,model_id:ModelId)->bool{ + self.unordered_checkpoints.contains(&model_id) + } +} +#[derive(Default)] +pub struct StageUpdate{ + //other behaviour models of this stage can have + ordered_checkpoints:HashMap, + unordered_checkpoints:HashSet, +} +impl Updatable for Stage{ + fn update(&mut self,update:StageUpdate){ + self.ordered_checkpoints.extend(update.ordered_checkpoints); + self.unordered_checkpoints.extend(update.unordered_checkpoints); + } +} + +#[derive(Clone,Copy,Hash,Eq,PartialEq)] +pub enum Zone{ + Start, + Finish, + Anticheat, +} +#[derive(Clone,Copy,Debug,Hash,id::Id,Eq,PartialEq,Ord,PartialOrd)] +pub struct ModeId(u32); +impl ModeId{ + pub const MAIN:Self=Self(0); + pub const BONUS:Self=Self(1); +} +#[derive(Clone)] +pub struct Mode{ + style:gameplay_style::StyleModifiers, + start:ModelId,//when you press reset you go here + zones:HashMap, + stages:Vec,//when you load the map you go to stages[0].spawn + //mutually exlusive stage element behaviour + elements:HashMap, +} +impl Mode{ + pub fn new( + style:gameplay_style::StyleModifiers, + start:ModelId, + zones:HashMap, + stages:Vec, + elements:HashMap, + )->Self{ + Self{ + style, + start, + zones, + stages, + elements, + } + } + pub fn empty(style:gameplay_style::StyleModifiers,start:ModelId)->Self{ + Self{ + style, + start, + zones:HashMap::new(), + stages:Vec::new(), + elements:HashMap::new(), + } + } + pub fn into_inner(self)->( + gameplay_style::StyleModifiers, + ModelId, + HashMap, + Vec, + HashMap, + ){ + ( + self.style, + self.start, + self.zones, + self.stages, + self.elements, + ) + } + pub const fn get_start(&self)->ModelId{ + self.start + } + pub const fn get_style(&self)->&gameplay_style::StyleModifiers{ + &self.style + } + pub fn push_stage(&mut self,stage:Stage){ + self.stages.push(stage) + } + pub fn get_stage_mut(&mut self,stage:StageId)->Option<&mut Stage>{ + self.stages.get_mut(stage.0 as usize) + } + pub fn get_spawn_model_id(&self,stage:StageId)->Option{ + self.stages.get(stage.0 as usize).map(|s|s.spawn) + } + pub fn get_zone(&self,model_id:ModelId)->Option<&Zone>{ + self.zones.get(&model_id) + } + pub fn get_stage(&self,stage_id:StageId)->Option<&Stage>{ + self.stages.get(stage_id.0 as usize) + } + pub fn get_element(&self,model_id:ModelId)->Option<&StageElement>{ + self.elements.get(&model_id) + } + //TODO: put this in the SNF + pub fn denormalize_data(&mut self){ + //expand and index normalized data + self.zones.insert(self.start,Zone::Start); + for (stage_id,stage) in self.stages.iter().enumerate(){ + self.elements.insert(stage.spawn,StageElement{ + stage_id:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::SpawnAt, + jump_limit:None, + }); + for (_,&model) in &stage.ordered_checkpoints{ + self.elements.insert(model,StageElement{ + stage_id:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::Checkpoint, + jump_limit:None, + }); + } + for &model in &stage.unordered_checkpoints{ + self.elements.insert(model,StageElement{ + stage_id:StageId(stage_id as u32), + force:false, + behaviour:StageElementBehaviour::Checkpoint, + jump_limit:None, + }); + } + } + } +} +//this would be nice as a macro +#[derive(Default)] +pub struct ModeUpdate{ + zones:HashMap, + stages:HashMap, + //mutually exlusive stage element behaviour + elements:HashMap, +} +impl Updatable for Mode{ + fn update(&mut self,update:ModeUpdate){ + self.zones.extend(update.zones); + for (stage,stage_update) in update.stages{ + if let Some(stage)=self.stages.get_mut(stage.0 as usize){ + stage.update(stage_update); + } + } + self.elements.extend(update.elements); + } +} +impl ModeUpdate{ + pub fn zone(model_id:ModelId,zone:Zone)->Self{ + let mut mu=Self::default(); + mu.zones.insert(model_id,zone); + mu + } + pub fn stage(stage_id:StageId,stage_update:StageUpdate)->Self{ + let mut mu=Self::default(); + mu.stages.insert(stage_id,stage_update); + mu + } + pub fn element(model_id:ModelId,element:StageElement)->Self{ + let mut mu=Self::default(); + mu.elements.insert(model_id,element); + mu + } + pub fn map_stage_element_idsStageId>(&mut self,f:F){ + for (_,stage_element) in self.elements.iter_mut(){ + stage_element.stage_id=f(stage_element.stage_id); + } + } +} + +#[derive(Default,Clone)] +pub struct Modes{ + pub modes:Vec, +} +impl Modes{ + pub const fn new(modes:Vec)->Self{ + Self{ + modes, + } + } + pub fn into_inner(self)->Vec{ + self.modes + } + pub fn push_mode(&mut self,mode:Mode){ + self.modes.push(mode) + } + pub fn get_mode(&self,mode:ModeId)->Option<&Mode>{ + self.modes.get(mode.0 as usize) + } +} +pub struct ModesUpdate{ + modes:HashMap, +} +impl Updatable for Modes{ + fn update(&mut self,update:ModesUpdate){ + for (mode,mode_update) in update.modes{ + if let Some(mode)=self.modes.get_mut(mode.0 as usize){ + mode.update(mode_update); + } + } + } +} diff --git a/lib/common/src/gameplay_style.rs b/lib/common/src/gameplay_style.rs new file mode 100644 index 0000000..d56fa50 --- /dev/null +++ b/lib/common/src/gameplay_style.rs @@ -0,0 +1,611 @@ +const VALVE_SCALE:Planar64=Planar64::raw(1<<28);// 1/16 + +use crate::integer::{int,vec3::int as int3,Time,Ratio64,Planar64,Planar64Vec3}; +use crate::controls_bitflag::Controls; + +#[derive(Clone,Debug)] +pub struct StyleModifiers{ + //controls which are allowed to pass into gameplay (usually all) + pub controls_mask:Controls, + //controls which are masked from control state (e.g. !jump in scroll style) + pub controls_mask_state:Controls, + //strafing + pub strafe:Option, + //player gets a controllable rocket force + pub rocket:Option, + //flying + //pub move_type:MoveType::Fly(FlySettings) + //MoveType::Physics(PhysicsSettings) -> PhysicsSettings (strafe,rocket,jump,walk,ladder,swim,gravity) + //jumping is allowed + pub jump:Option, + //standing & walking is allowed + pub walk:Option, + //laddering is allowed + pub ladder:Option, + //water propulsion + pub swim:Option, + //maximum slope before sloped surfaces become frictionless + pub gravity:Planar64Vec3, + //hitbox + pub hitbox:Hitbox, + //camera location relative to the center (0,0,0) of the hitbox + pub camera_offset:Planar64Vec3, + //unused + pub mass:Planar64, +} +impl std::default::Default for StyleModifiers{ + fn default()->Self{ + Self::roblox_bhop() + } +} + +#[derive(Clone,Debug)] +pub enum JumpCalculation{ + Max,//Roblox: jumped_speed=max(velocity.boost(),velocity.jump()) + BoostThenJump,//jumped_speed=velocity.boost().jump() + JumpThenBoost,//jumped_speed=velocity.jump().boost() +} + +#[derive(Clone,Debug)] +pub enum JumpImpulse{ + Time(Time),//jump time is invariant across mass and gravity changes + Height(Planar64),//jump height is invariant across mass and gravity changes + Linear(Planar64),//jump velocity is invariant across mass and gravity changes + Energy(Planar64),// :) +} +//Jumping acts on dot(walks_state.normal,body.velocity) +//Energy means it adds energy +//Linear means it linearly adds on +impl JumpImpulse{ + pub fn jump( + &self, + velocity:Planar64Vec3, + jump_dir:Planar64Vec3, + gravity:&Planar64Vec3, + mass:Planar64, + )->Planar64Vec3{ + match self{ + &JumpImpulse::Time(time)=>velocity-(*gravity*time).map(|t|t.divide().fix_1()), + &JumpImpulse::Height(height)=>{ + //height==-v.y*v.y/(2*g.y); + //use energy to determine max height + let gg=gravity.length_squared(); + let g=gg.sqrt().fix_1(); + let v_g=gravity.dot(velocity); + //do it backwards + let radicand=v_g*v_g+(g*height*2).fix_4(); + velocity-(*gravity*(radicand.sqrt().fix_2()+v_g)/gg).divide().fix_1() + }, + &JumpImpulse::Linear(jump_speed)=>velocity+(jump_dir*jump_speed/jump_dir.length()).divide().fix_1(), + &JumpImpulse::Energy(energy)=>{ + //calculate energy + //let e=gravity.dot(velocity); + //add + //you get the idea + todo!() + }, + } + } + //TODO: remove this and implement JumpCalculation properly + pub fn get_jump_deltav(&self,gravity:&Planar64Vec3,mass:Planar64)->Planar64{ + //gravity.length() is actually the proper calculation because the jump is always opposite the gravity direction + match self{ + &JumpImpulse::Time(time)=>(gravity.length().fix_1()*time/2).divide().fix_1(), + &JumpImpulse::Height(height)=>(gravity.length()*height*2).sqrt().fix_1(), + &JumpImpulse::Linear(deltav)=>deltav, + &JumpImpulse::Energy(energy)=>(energy.sqrt()*2/mass.sqrt()).divide().fix_1(), + } + } +} + +#[derive(Clone,Debug)] +pub struct JumpSettings{ + //information used to calculate jump power + pub impulse:JumpImpulse, + //information used to calculate jump behaviour + pub calculation:JumpCalculation, + //limit the minimum jump power when combined with downwards momentum + //This is true in both roblox and source + pub limit_minimum:bool, +} +impl JumpSettings{ + pub fn jumped_velocity( + &self, + style:&StyleModifiers, + jump_dir:Planar64Vec3, + rel_velocity:Planar64Vec3, + booster:Option<&crate::gameplay_attributes::Booster>, + )->Planar64Vec3{ + let jump_speed=self.impulse.get_jump_deltav(&style.gravity,style.mass); + match (self.limit_minimum,&self.calculation){ + (true,JumpCalculation::Max)=>{ + //the roblox calculation + let boost_vel=match booster{ + Some(booster)=>booster.boost(rel_velocity), + None=>rel_velocity, + }; + let j=boost_vel.dot(jump_dir); + let js=jump_speed.fix_2(); + if j{ + //the source calculation (?) + let boost_vel=match booster{ + Some(booster)=>booster.boost(rel_velocity), + None=>rel_velocity, + }; + let j=boost_vel.dot(jump_dir); + let js=jump_speed.fix_2(); + if j{ + //??? calculation + //max(boost_vel,jump_vel) + let boost_vel=match booster{ + Some(booster)=>booster.boost(rel_velocity), + None=>rel_velocity, + }; + let boost_dot=boost_vel.dot(jump_dir); + let js=jump_speed.fix_2(); + if boost_dot{ + let boost_vel=match booster{ + Some(booster)=>booster.boost(rel_velocity), + None=>rel_velocity, + }; + boost_vel+jump_dir.with_length(jump_speed).divide().fix_1() + }, + } + } +} + +#[derive(Clone,Debug)] +pub struct ControlsActivation{ + //allowed keys + pub controls_mask:Controls, + //allow strafing only if any of the masked controls are held, eg W|S for shsw + pub controls_intersects:Controls, + //allow strafing only if all of the masked controls are held, eg W for hsw, w-only + pub controls_contains:Controls, + //Function(Boxbool>), +} +impl ControlsActivation{ + pub const fn mask(&self,controls:Controls)->Controls{ + controls.intersection(self.controls_mask) + } + pub const fn activates(&self,controls:Controls)->bool{ + (self.controls_intersects.is_empty()||controls.intersects(self.controls_intersects)) + &&controls.contains(self.controls_contains) + } + pub const fn full_3d()->Self{ + Self{ + controls_mask:Controls::wasdqe(), + controls_intersects:Controls::wasdqe(), + controls_contains:Controls::empty(), + } + } + //classical styles + //Normal + pub const fn full_2d()->Self{ + Self{ + controls_mask:Controls::wasd(), + controls_intersects:Controls::wasd(), + controls_contains:Controls::empty(), + } + } + //Sideways + pub const fn sideways()->Self{ + Self{ + controls_mask:Controls::MoveForward.union(Controls::MoveBackward), + controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), + controls_contains:Controls::empty(), + } + } + //Half-Sideways + pub const fn half_sideways()->Self{ + Self{ + controls_mask:Controls::MoveForward.union(Controls::MoveLeft).union(Controls::MoveRight), + controls_intersects:Controls::MoveLeft.union(Controls::MoveRight), + controls_contains:Controls::MoveForward, + } + } + //Surf Half-Sideways + pub const fn surf_half_sideways()->Self{ + Self{ + controls_mask:Controls::MoveForward.union(Controls::MoveBackward).union(Controls::MoveLeft).union(Controls::MoveRight), + controls_intersects:Controls::MoveForward.union(Controls::MoveBackward), + controls_contains:Controls::empty(), + } + } + //W-Only + pub const fn w_only()->Self{ + Self{ + controls_mask:Controls::MoveForward, + controls_intersects:Controls::empty(), + controls_contains:Controls::MoveForward, + } + } + //A-Only + pub const fn a_only()->Self{ + Self{ + controls_mask:Controls::MoveLeft, + controls_intersects:Controls::empty(), + controls_contains:Controls::MoveLeft, + } + } + //Backwards +} + +#[derive(Clone,Debug)] +pub struct StrafeSettings{ + pub enable:ControlsActivation, + pub mv:Planar64, + pub air_accel_limit:Option, + pub tick_rate:Ratio64, +} +impl StrafeSettings{ + pub fn tick_velocity(&self,velocity:Planar64Vec3,control_dir:Planar64Vec3)->Option{ + let d=velocity.dot(control_dir); + let mv=self.mv.fix_2(); + match dSome(velocity+(control_dir*self.air_accel_limit.map_or(mv-d,|limit|limit.fix_2().min(mv-d))).fix_1()), + false=>None, + } + } + pub fn next_tick(&self,time:Time)->Time{ + Time::from_nanos(self.tick_rate.rhs_div_int(self.tick_rate.mul_int(time.nanos())+1)) + } + pub const fn activates(&self,controls:Controls)->bool{ + self.enable.activates(controls) + } + pub const fn mask(&self,controls:Controls)->Controls{ + self.enable.mask(controls) + } +} + +#[derive(Clone,Debug)] +pub struct PropulsionSettings{ + pub magnitude:Planar64, +} +impl PropulsionSettings{ + pub fn acceleration(&self,control_dir:Planar64Vec3)->Planar64Vec3{ + (control_dir*self.magnitude).fix_1() + } +} + +#[derive(Clone,Debug)] +pub struct AccelerateSettings{ + pub accel:Planar64, + pub topspeed:Planar64, +} +#[derive(Clone,Debug)] +pub struct WalkSettings{ + pub accelerate:AccelerateSettings, + pub static_friction:Planar64, + pub kinetic_friction:Planar64, + //if a surf slope angle does not exist, then everything is slippery and walking is impossible + pub surf_dot:Planar64,//surf_dotPlanar64{ + //TODO: fallible walk accel + let diff_len=target_diff.length().fix_1(); + let friction=if diff_lenPlanar64Vec3{ + if control_dir==crate::integer::vec3::ZERO{ + return control_dir; + } + let nn=normal.length_squared(); + let mm=control_dir.length_squared(); + let nnmm=nn*mm; + let d=normal.dot(control_dir); + let dd=d*d; + if ddbool{ + //normal is not guaranteed to be unit length + let ny=normal.dot(up); + let h=normal.length().fix_1(); + //remember this is a normal vector + ny.is_positive()&&h*self.surf_dotPlanar64{ + //TODO: fallible ladder accel + self.accelerate.accel + } + pub fn get_ladder_target_velocity(&self,mut control_dir:Planar64Vec3,normal:Planar64Vec3)->Planar64Vec3{ + if control_dir==crate::integer::vec3::ZERO{ + return control_dir; + } + let nn=normal.length_squared(); + let mm=control_dir.length_squared(); + let nnmm=nn*mm; + let d=normal.dot(control_dir); + let mut dd=d*d; + if (self.dot*self.dot*nnmm).fix_4()Self{ + Self{ + halfsize:int3(2,5,2)>>1, + mesh:HitboxMesh::Cylinder, + } + } + pub fn source()->Self{ + Self{ + halfsize:((int3(33,73,33)>>1)*VALVE_SCALE).fix_1(), + mesh:HitboxMesh::Box, + } + } +} + +impl StyleModifiers{ + pub const RIGHT_DIR:Planar64Vec3=crate::integer::vec3::X; + pub const UP_DIR:Planar64Vec3=crate::integer::vec3::Y; + pub const FORWARD_DIR:Planar64Vec3=crate::integer::vec3::NEG_Z; + + pub fn neo()->Self{ + Self{ + controls_mask:Controls::all(), + controls_mask_state:Controls::all(), + strafe:Some(StrafeSettings{ + enable:ControlsActivation::full_2d(), + air_accel_limit:None, + mv:int(3), + tick_rate:Ratio64::new(64,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump:Some(JumpSettings{ + impulse:JumpImpulse::Energy(int(512)), + calculation:JumpCalculation::JumpThenBoost, + limit_minimum:false, + }), + gravity:int3(0,-80,0), + mass:int(1), + rocket:None, + walk:Some(WalkSettings{ + accelerate:AccelerateSettings{ + topspeed:int(16), + accel:int(80), + }, + static_friction:int(2), + kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static + surf_dot:int(3)/4, + }), + ladder:Some(LadderSettings{ + accelerate:AccelerateSettings{ + topspeed:int(16), + accel:int(160), + }, + dot:(int(1)/2).sqrt(), + }), + swim:Some(PropulsionSettings{ + magnitude:int(12), + }), + hitbox:Hitbox::roblox(), + camera_offset:int3(0,2,0),//4.5-2.5=2 + } + } + + pub fn roblox_bhop()->Self{ + Self{ + controls_mask:Controls::all(), + controls_mask_state:Controls::all(), + strafe:Some(StrafeSettings{ + enable:ControlsActivation::full_2d(), + air_accel_limit:None, + mv:int(27)/10, + tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump:Some(JumpSettings{ + impulse:JumpImpulse::Time(Time::from_micros(715_588)), + calculation:JumpCalculation::Max, + limit_minimum:true, + }), + gravity:int3(0,-100,0), + mass:int(1), + rocket:None, + walk:Some(WalkSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18), + accel:int(90), + }, + static_friction:int(2), + kinetic_friction:int(3),//unrealistic: kinetic friction is typically lower than static + surf_dot:int(3)/4,// normal.y=0.75 + }), + ladder:Some(LadderSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18), + accel:int(180), + }, + dot:(int(1)/2).sqrt(), + }), + swim:Some(PropulsionSettings{ + magnitude:int(12), + }), + hitbox:Hitbox::roblox(), + camera_offset:int3(0,2,0),//4.5-2.5=2 + } + } + pub fn roblox_surf()->Self{ + Self{ + gravity:int3(0,-50,0), + ..Self::roblox_bhop() + } + } + pub fn roblox_rocket()->Self{ + Self{ + strafe:None, + rocket:Some(PropulsionSettings{ + magnitude:int(200), + }), + ..Self::roblox_bhop() + } + } + + pub fn source_bhop()->Self{ + Self{ + controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, + controls_mask_state:Controls::all(), + strafe:Some(StrafeSettings{ + enable:ControlsActivation::full_2d(), + air_accel_limit:Some(Planar64::raw(150<<28)*100), + mv:(Planar64::raw(30)*VALVE_SCALE).fix_1(), + tick_rate:Ratio64::new(100,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump:Some(JumpSettings{ + impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), + calculation:JumpCalculation::JumpThenBoost, + limit_minimum:true, + }), + gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), + mass:int(1), + rocket:None, + walk:Some(WalkSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18),//? + accel:int(90),//? + }, + static_friction:int(2),//? + kinetic_friction:int(3),//? + surf_dot:int(3)/4,// normal.y=0.75 + }), + ladder:Some(LadderSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18),//? + accel:int(180),//? + }, + dot:(int(1)/2).sqrt(),//? + }), + swim:Some(PropulsionSettings{ + magnitude:int(12),//? + }), + hitbox:Hitbox::source(), + camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), + } + } + pub fn source_surf()->Self{ + Self{ + controls_mask:Controls::all()-Controls::MoveUp-Controls::MoveDown, + controls_mask_state:Controls::all(), + strafe:Some(StrafeSettings{ + enable:ControlsActivation::full_2d(), + air_accel_limit:Some((int(150)*66*VALVE_SCALE).fix_1()), + mv:(int(30)*VALVE_SCALE).fix_1(), + tick_rate:Ratio64::new(66,Time::ONE_SECOND.nanos() as u64).unwrap(), + }), + jump:Some(JumpSettings{ + impulse:JumpImpulse::Height((int(52)*VALVE_SCALE).fix_1()), + calculation:JumpCalculation::JumpThenBoost, + limit_minimum:true, + }), + gravity:(int3(0,-800,0)*VALVE_SCALE).fix_1(), + mass:int(1), + rocket:None, + walk:Some(WalkSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18),//? + accel:int(90),//? + }, + static_friction:int(2),//? + kinetic_friction:int(3),//? + surf_dot:int(3)/4,// normal.y=0.75 + }), + ladder:Some(LadderSettings{ + accelerate:AccelerateSettings{ + topspeed:int(18),//? + accel:int(180),//? + }, + dot:(int(1)/2).sqrt(),//? + }), + swim:Some(PropulsionSettings{ + magnitude:int(12),//? + }), + hitbox:Hitbox::source(), + camera_offset:((int3(0,64,0)-(int3(0,73,0)>>1))*VALVE_SCALE).fix_1(), + } + } +} diff --git a/lib/common/src/instruction.rs b/lib/common/src/instruction.rs new file mode 100644 index 0000000..df07598 --- /dev/null +++ b/lib/common/src/instruction.rs @@ -0,0 +1,53 @@ +use crate::integer::Time; + +#[derive(Debug)] +pub struct TimedInstruction{ + pub time:Time, + pub instruction:I, +} + +pub trait InstructionEmitter{ + fn next_instruction(&self,time_limit:Time)->Option>; +} +pub trait InstructionConsumer{ + fn process_instruction(&mut self, instruction:TimedInstruction); +} + +//PROPER PRIVATE FIELDS!!! +pub struct InstructionCollector{ + time:Time, + instruction:Option, +} +impl InstructionCollector{ + pub const fn new(time:Time)->Self{ + Self{ + time, + instruction:None + } + } + #[inline] + pub const fn time(&self)->Time{ + self.time + } + pub fn collect(&mut self,instruction:Option>){ + match instruction{ + Some(unwrap_instruction)=>{ + if unwrap_instruction.time(), + } + } + pub fn instruction(self)->Option>{ + //STEAL INSTRUCTION AND DESTROY INSTRUCTIONCOLLECTOR + match self.instruction{ + Some(instruction)=>Some(TimedInstruction{ + time:self.time, + instruction + }), + None=>None, + } + } +} diff --git a/lib/common/src/integer.rs b/lib/common/src/integer.rs new file mode 100644 index 0000000..670102f --- /dev/null +++ b/lib/common/src/integer.rs @@ -0,0 +1,664 @@ +pub use fixed_wide::fixed::{Fixed,Fix}; +pub use ratio_ops::ratio::{Ratio,Divide}; + +//integer units +#[derive(Clone,Copy,Hash,Eq,PartialEq,PartialOrd,Debug)] +pub struct Time(i64); +impl Time{ + pub const MIN:Self=Self(i64::MIN); + pub const MAX:Self=Self(i64::MAX); + pub const ZERO:Self=Self(0); + pub const ONE_SECOND:Self=Self(1_000_000_000); + pub const ONE_MILLISECOND:Self=Self(1_000_000); + pub const ONE_MICROSECOND:Self=Self(1_000); + pub const ONE_NANOSECOND:Self=Self(1); + #[inline] + pub const fn raw(num:i64)->Self{ + Self(num) + } + #[inline] + pub const fn get(self)->i64{ + self.0 + } + #[inline] + pub const fn from_secs(num:i64)->Self{ + Self(Self::ONE_SECOND.0*num) + } + #[inline] + pub const fn from_millis(num:i64)->Self{ + Self(Self::ONE_MILLISECOND.0*num) + } + #[inline] + pub const fn from_micros(num:i64)->Self{ + Self(Self::ONE_MICROSECOND.0*num) + } + #[inline] + pub const fn from_nanos(num:i64)->Self{ + Self(Self::ONE_NANOSECOND.0*num) + } + //should I have checked subtraction? force all time variables to be positive? + #[inline] + pub const fn nanos(self)->i64{ + self.0 + } + #[inline] + pub const fn to_ratio(self)->Ratio{ + Ratio::new(Planar64::raw(self.0),Planar64::raw(1_000_000_000)) + } +} +impl From for Time{ + #[inline] + fn from(value:Planar64)->Self{ + Time((value*Planar64::raw(1_000_000_000)).fix_1().to_raw()) + } +} +impl From> for Time + where + Num:core::ops::Mul, + N1:Divide, + T1:Fix, +{ + #[inline] + fn from(value:Ratio)->Self{ + Time((value*Planar64::raw(1_000_000_000)).divide().fix().to_raw()) + } +} +impl std::fmt::Display for Time{ + #[inline] + fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{ + write!(f,"{}s+{:09}ns",self.0/Self::ONE_SECOND.0,self.0%Self::ONE_SECOND.0) + } +} +impl std::default::Default for Time{ + fn default()->Self{ + Self(0) + } +} +impl std::ops::Neg for Time{ + type Output=Time; + #[inline] + fn neg(self)->Self::Output { + Time(-self.0) + } +} +macro_rules! impl_time_additive_operator { + ($trait:ty, $method:ident) => { + impl $trait for Time{ + type Output=Time; + #[inline] + fn $method(self,rhs:Self)->Self::Output { + Time(self.0.$method(rhs.0)) + } + } + }; +} +impl_time_additive_operator!(core::ops::Add,add); +impl_time_additive_operator!(core::ops::Sub,sub); +impl_time_additive_operator!(core::ops::Rem,rem); +macro_rules! impl_time_additive_assign_operator { + ($trait:ty, $method:ident) => { + impl $trait for Time{ + #[inline] + fn $method(&mut self,rhs:Self){ + self.0.$method(rhs.0) + } + } + }; +} +impl_time_additive_assign_operator!(core::ops::AddAssign,add_assign); +impl_time_additive_assign_operator!(core::ops::SubAssign,sub_assign); +impl_time_additive_assign_operator!(core::ops::RemAssign,rem_assign); +impl std::ops::Mul for Time{ + type Output=Ratio,fixed_wide::fixed::Fixed<2,64>>; + #[inline] + fn mul(self,rhs:Self)->Self::Output{ + Ratio::new(Fixed::raw(self.0)*Fixed::raw(rhs.0),Fixed::raw_digit(1_000_000_000i64.pow(2))) + } +} +impl std::ops::Div for Time{ + type Output=Time; + #[inline] + fn div(self,rhs:i64)->Self::Output{ + Time(self.0/rhs) + } +} +impl std::ops::Mul for Time{ + type Output=Time; + #[inline] + fn mul(self,rhs:i64)->Self::Output{ + Time(self.0*rhs) + } +} +impl core::ops::Mul