Coder Social home page Coder Social logo

rustarok's People

Contributors

atul9 avatar balazsbodi avatar bbodi avatar vsoch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rustarok's Issues

2019W30

2019W30

This is the first weekly blog post about the project.

This was a very productive week, I spent around 30 hours on the project.

Status system

The biggest achievement for this week is definitely the Status system. You can find the design document for it here, and I plan to write a follow-up post about the difficulties and differences from the original design.

Some gifs demonstrating the capabilities of the new Status system:

  • Mounts (press 'Z')
    mount
  • new skill: FireBomb. Can be put on characters. After two seconds, it explodes, damaging the target and its area. Additionally, a FireBomb is applied to the damaged neighbour characters as well.
    firebomb
  • new skills: Poison and Cure. Poison applies poison damages on the target in every second. Cure removes Poison.
    poison
  • new skill: AbsorbShield. Absorbs every damage for 2 seconds, then heals the target for the amount of damage it has absorbed.
    absorb

Other achievements

  • A README file was created for the project on github

  • Design documentation about Statuses and the new Graphic system

  • Camera follows the character (can be turned on/off by pressing 'L')

  • "Remaining time for status" bar above the character (the orange decreasing bar in firebomb.gif)

  • 'absorb' and 'block' texts are rendered if the attack was absorbed or blocked.

  • Statuses can modify incoming damages on characters (decrease their values, block/absorb them etc)

  • Font loading and basic text rendering

  • Faster walking speed makes walking animation faster

  • Skill casting animation has changed: Only the first frame is rendered during casting, and when it is finished, the whole animation is played. Now this post animation can be interrupted by other actions (e.g. casting other skill or walk away), but in the future I might introduce a mandatory delay after casting.

  • Firewall pushes back contacting entities again
    firewall_push

  • Skill bar

  • Rendering skill name during target selection

  • Browser clients and streaming works again (ultra basic and inefficient yet) (low resolution is because of github's 10MB file size limit)
    streaming2

  • Embedded web server for browser clients

  • Self casting (ALT + skill key)

Where does rustarok need write?

I'm finishing up a version in a container for linux, and the last bit that I need to know is where exactly the game expects to have write. I know that cargo/debug/.cargo-lock needs write, but the error message doesn't tell me where else it would be needed:

Singularity rustarok_latest.sif:/code> cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.59s
     Running `target/debug/rustarok`
[00:00:15.836] (7f8d990edf40) INFO   GRF loading: 15836ms
[00:00:16.444] (7f8d990edf40) INFO   rsw loaded: 144ms
[src/asset/gat.rs:137] non_walkable_cells.iter().filter(|&&it| it).count() = 60788
[src/asset/gat.rs:184] rectangles.len() = 1317
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 30, kind: Other, message: "Read-only file system" }', src/libcore/result.rs:1084:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

As soon as I know this, I think it should work!

Implement Statuses

Statuses

First, by status, I mean supportive or destructive effects on an entity, like buffs, stun, frozen, poison etc.
They should be able to

  • run some game logic in every frame (e.g. poison damages the target entity in every X seconds)
  • modify the target entity's attributes (increase/decrease attack speed, attack power, moving speed, etc)
  • modify the target entity's ability to move/cast skills (e.g. frozen, stun stops entities from moving)
  • change the render logic of the target (e.g. for poison, the target should be green, for frozen state, an ice effect should be rendered on the sprite)
  • remove existing statuses from the target entity (e.g. stun and sleep can't be on the same target at once)

Multiple statuses can have different effects on rendering the entity, e.g. one Buff can increase the size of the entity, and a Poison can make it green. In that case we have to render an increased, green sprite. If that entity then gets stunned, then we have to render the stun effect above the increased, green sprite (so the y offset of the stun effect will be different than for a normal sprite).

Immediate or retained calculation

Player attributes (atk, armor etc) are modified by statuses. The naive approach would be to have for example an atk field on entity, then when a Buff status is applied on that entity, it would modify that atk field, like adding +10% to it.

However it would lead to unmaintainable and complicated code.

  • You would have to reset the original value after the Buff has expired, which is almost impossible. Imagine the situation where you have two buffs on your target, one which increases atk by 100, then the other by +10%. What if the first Buf expires earlier than the second one, and subtract 100 from the current atk (which is increased by 10%), and then the second one removes 10% from the reduced atk.
  • You would not know why atk has a specific value. If there is a bug, and some status forgets to clean up his work, it is more difficult to find that bug.
  • Sometimes you need the base atk of a character, so you can't just modify it.
  • etc

So rather I choose immediate calculation, where entity attributes are calculated on demand, in every frame if necessary. It would have a base attribute data structure, which then would be copied and modified by all the active Statuses on the entity. That copied attribute structure will be used for damage calculations.

Algorithmic behaviour

My assumption is that there won't be much Status on an entity at the same time, maximum ~10. In a team fight, they change rapidly, then have a bigger time span when they don't change at all (e.g. go into a team fight will add/remove many statuses on almost all the players. After and before the fight it almost does not change at all).

My approach

"Important" and not so "important" Statuses

Many skills and effects will be implemented by statuses, which first does not seem as a Status. E.g. a wizard put a fire bomb on a character. Before expiration, it renders some fire effect on the character, then on expiration it damages the character and its area. This is a perfect example for a Status, however it is rather strongly related to the "fire bomb" skill, and I would put the code of this Status in the same file as the skill which created the status.

Since some game logic has to figure out if a specific Status is applied on an entity (e.g. some skills damages more on Stunned entities), some States have to have an identifier, a "type". But I would not give an own enum for every "not so important" status, since they won't be queried for and other parts of the code does not have to know about them.

So to avoid polluting the code with those Skill-related statuses, I distinguish "important" and "not so important" Statuses.

Important Statuses are the ones who are known globally, does not relate to one concrete skill, and used heavily by other codes, statuses, calculations etc. For example:

  • Stun
  • Frozen
  • Rooted
  • Sleep
  • etc

Design

The enum will look something like this:

pub enum StatusType {
  Stun(ElapsedTime),
  Frozen(ElapsedTime),
  Root(ElapsedTime),
  Sleep(ElapsedTime),
  // "not so important" statuses
  OtherHarmfulStatus(Box<impl Status>),
  OtherSupportiveStatus(Box<impl Status>)
}

With this enum, it is possible to implement skills like "remove all negative status from the target".

Status will be a trait with functions like

  • fn can_target_move -> bool
  • fn can_target_cast -> bool (e.g. being rooted disallows moving but allows casting skills)
  • fn get_render_effect(&self) -> ??? (It might be necessary to render more than one effect on the target, so the return type is in question)
  • fn get_render_color -> [f32; 4]
  • fn get_render_size -> f32
  • fn calc_attribs(&self, &mut CharAttribs)
  • fn update(&mut self, now: ElapsedTime)
  • fn get_duration_percent_for_rendering() -> Option<f32>: This is for rendering a decreasing status bar above the character which tells when the next status will expire

Entities will have a Vec<Status> field.

Then:

  • Statuses go into the Vec in insertion order
  • Many parts of the code will iterate through the Vec and call those functions to determine attributes and rendering properties of the target entity
  • They will remove themselves when it is necessary
  • Querying an entity for a specific status requires linear searching

To avoid elements to be moved when Statuses are added to the Vec, I might use an [Option<Status>; 32] instead.

Implement these to test the design:

  • Buff + Poison + Stun
    • Create a buff skill which increase the size of the target
    • Create a poison status. It damages the player in every second, and makes it green.
    • Implement the stun status. The stun effect must appear above the increased size sprite.
  • Buff + Poison + Frozen
    • Same as above, but the Frozen effect sprite must also be increased
  • Fire Bomb
    • A skill which can be casted on an enemy.
    • After x seconds, it explodes and damages the target and its area
    • A "decreasing casting bar" should appear above the character to show when the status will expire
  • Frozen state should be broken when the target is attacked.

Graphics refactor

Graphics

Here, the term entity is used as in Entity Component Systems.

Who should initiate rendering sprites/effects

Currently, utilizing the Entity Component System, specific entities are responsible for rendering Sprites or Effects.
E.g. a skill, which wish to render a lightning bolt on an area, has to create an entity with the details of the animation (when it started, what is the effect id, when it ends etc), then a system will pick up that entity and render it.

impl PushBackWallSkill {
    pub fn new(...) {
		let effect_comp = StrEffectComponent {
                effect: "StrEffect::FireWall".to_owned(),
                pos: effect_coords,
                start_time: system_time,
                die_at: system_time.add_seconds(3.0)
        };
        let effect_entity = entities.create();
        updater.insert(effect_entity, effect_comp);
}

Later, the skill is responsible for removing the effect entity:

impl SkillManifestation for PushBackWallSkill {
  ..
  fn update(&mut self,
            self_entity_id: Entity,
            updater: &mut specs::Write<LazyUpdate>,
            ...) {
        if self.die_at.has_passed(system_vars.time) {
            updater.remove::<SkillManifestationComponent>(self_entity_id);
            for effect_id in &self.effect_ids {
                updater.remove::<StrEffectComponent>(*effect_id);
            }

The other option would be to initiate rendering directly in the update function of the skill.
e.g.

impl SkillManifestation for PushBackWallSkill {
	fn update(..) {
        ...
        render_sys.render_effect(
            "StrEffect::FireWall",
            effect_coords,
            self.effect_started,
            self.effect_ends_at
        );
	}
}

One advantage of the first approach is cache utilization. StrEffectComponent can be stored in a Vec, so the system who will render those components can iterate through them easily, achieving great cache utilization.
An other advantage is "fire and forget" rendering: It is enough to add an effect to the system once, and the effect will appear and be animated without any further effort.
The disadvantage of it is that the skill- and its visualization logic are separated.

  • The skill has to clean up after itself. It is possible to forget to remove an effect component.
  • That "fire and forget" makes it more difficult to understand the code. The update function of a skill won't reflect the fact that an animation is rendered for that skill.

The advantage is not that obvious anymore if we consider real world scenarios, like

  • What if the effect requires special positioning, like moving in a direction in every frame. With this approach the update code of the skill has to get the EffectComponent and modify its position.
  • Many effects are put on players/monsters, and the effect have to follow their positions, which means the rendering system has to access other additional components too for their positions.
  • I am planning to redesign and refactor the graphics module (see later), which basically will translate graphic calls to Command structures, then the rendering system can optimize and render those Commands, so cache utilization advantage would be moved to the the render system level anyway with either approach.

Personally I like the second approach more. It feels that effects/sprites in this situation belongs to the skill, and it is the skill's responsibility to manage them. However I would keep StrEffectComponent, but for simpler cases, like when I want to put an effect on the map, which does not have any "owner", just stands there alone (e.g. for debugging purposes, to check how an effect look like on the map).

As an explanation on why I chose to put rendering data into the Skill, but I don't do it in case of Characters for example, is that Skills are stored as a Box object anyway.
Almost every Skill has a different data structure and update logic, and their living instance numbers will be very few, so it is impossible to store them in a Vec. Which means I don't get any benefit separating the rendering logic from the Skills.
But in the case of Characters, both the Characters and their rendering data (SpriteRenderDescriptorComponent) have a homogene structure and stored in a Vec as components. And there are systems in the code which uses solely either one or the other, so they are not coupled strongly.

Graphics refactor

Systematic Refactor

Right now there isn't a separate graphic module in the code, different systems access OpenGL to render whatever they want.

  • It leads to ugly code. Systems have to be aware of Shaders and other rendering-related objects, which pollute their code.
  • To render some special cases, the rendered objects has to be prepared for it (e.g. for rendering transparent objects, they have to be sorted by their distance to the camera). It is difficult to do it if rendering logic is scattered throughout the code, or even more, if one system which render things does not even know about an other system who do the same.
  • As I wrote previously, I want to put rendering logic into more code (code of skills for example), so I will need some rendering system which provides a simple, high level API for those areas for rendering whatever they want.
  • It makes it more difficult to optimize rendering. 'Context switching' in rendering is expensive, so it is beneficial to render as many things with the same rendering attributes (texture, shader, vertex array etc) as possible at once. It is only possible if the objects to be rendered are collected at one place and grouped by rendering attributes.
  • The same with culling (not rendering things which are not seen by the camera)
  • A new, unified rendering module would make it possible to measure some characteristics, like the amount of rendered vertices, textures, sprites etc etc.
  • An experimental idea: (I haven't mentioned yet, but the original goal for solving the multiplayer part of the game is to stream the render outpout + sound to browser clients). If it turn out that sending pixels are too expensive, sending these high level rendering commands from the rendering system to the clients could be cheaper.

So the idea is to have one rendering layer which collects high level rendering commands (e.g. "draw a sprite here" or "draw a circle there").
It then processes, prepares and renders those commands.

Implementation

pub struct RenderCommandCollector
This will be a component, and every ControllerComponent (a controller represents a human player) will own an instance from it.
It will be responsible for collecting rendering commands from the systems. Currently I don't plan to put any preprocessing logic into this struct. The sorting will happen in the RenderSystem, before the rendering, not by this struct.
It will contain several Vecs to store specific commands, one for rendering models, one for sprites, one for circles etc.

pub struct RenderSystem

This is the system which is responsible for processing and rendering the commands obtained from RenderCommandCollectors.

It will have many cached VertexBuffers for dynamic objects.

Missing features

  • Cull ground (now all the ground is rendered) (however it does not seem something as heavy, measure first)
  • Experimenting with removing back face of models
  • Sort models by their Z coordinate on startup
  • Sort sprites and effects by their Z coordinate
  • Render dead sprites first
  • Group model rendering by their textures
  • Efficient primitive (circle, rectangle) drawing

Achievable goals:

  • 100 fire effect should be rendered without massive FPS drop
  • 100 flying number should be rendered without massive FPS drop
  • 100 mixed (heal, damage, crit etc) flying number should be rendered without massive FPS drop
  • 100 sprites should be rendered without massive FPS drop

Running on Linux?

Is this game supported for running on linux? If the Rustarok files can be distributed, we could make a container to serve it. Do you have any direct (programmatic) links for download? I'd like to try / play, but I use Linux.

Text rendering

Use SDL_TTF and create an atlas texture from the letters.

Should work both in 2d and 3d.

Sprite loading with and without RLE encoding seem to be doing the same thing

I'm trying to understand the code for sprite loading, and I've been struggling to understand the different implementations for loading indexed images with and without RLE encoding.

Both

SpriteFile::read_indexed_frames(&mut reader, indexed_frame_count)
and
SpriteFile::read_indexed_frames_rle(&mut reader, indexed_frame_count)
seem to be doing the same thing, with slightly different implementations:

 fn read_indexed_frames(buf: &mut BinaryReader, indexed_frame_count: usize) -> Vec<SprFrame> {
    (0..indexed_frame_count)
        .map(|_i| {
            let width = buf.next_u16();
            let height = buf.next_u16();
            let frame = SprFrame {
                typ: SpriteType::PAL,
                width: width as usize,
                height: height as usize,
                data_index: buf.tell(),
            };
            buf.skip(width as u32 * height as u32);
            //                buf.next(width as u32 * height as u32).to_vec();
            frame
        })
        .collect()
}
fn read_indexed_frames_rle(
    reader: &mut BinaryReader,
    indexed_frame_count: usize,
) -> Vec<SprFrame> {
    (0..indexed_frame_count)
        .map(|_i| {
            let width = reader.next_u16();
            let height = reader.next_u16();
            let data_index = reader.tell();
            let size = reader.next_u16();
            reader.skip(size as u32);
             SprFrame {
                typ: SpriteType::PAL,
                width: width as usize,
                height: height as usize,
                data_index,
            }
        })
        .collect()
}

Am I not seeing something, or maybe is this how they are actually supposed to be?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.