M client/src/api/game/mod.rs => client/src/api/game/mod.rs +14 -0
@@ 70,6 70,20 @@ pub enum Command {
         amount: usize,
         sides: usize,
     },
+    GivePlays {
+        amount: usize,
+    },
+    GiveBuys {
+        amount: usize,
+    },
+    GiveVP {
+        amount: usize,
+    },
+    /// mark a card in the targets deck to be trashed on play.
+    /// if index in None, a random card will be chosen
+    MarkCardInDeckToBeTrashed {
+        index: Option<usize>,
+    },
 }
 
 #[derive(Deserialize, Serialize, Clone, PartialEq)]
 
M client/src/game_status/mod.rs => client/src/game_status/mod.rs +4 -0
@@ 17,6 17,8 @@ mod parser;
 #[derive(Deserialize, Serialize, Clone, PartialEq)]
 pub struct CardAction {
     pub command: Command,
+    /// if false, targets the next player
+    pub target_self: bool,
 }
 
 #[derive(Deserialize, Serialize, Clone, PartialEq)]
@@ 30,6 32,8 @@ pub struct Card {
     /// if set, use this?
     //vp_cost: Option<u32>,
     pub actions: Vec<CardAction>,
+    /// trash after play?
+    pub to_be_trashed: bool,
 }
 
 /// a supply pile holds an amount of some card
 
M client/src/game_status/parser.rs => client/src/game_status/parser.rs +58 -16
@@ 19,14 19,11 @@ use crate::{
 use super::{GameStatus, PlayerState, PlayerStatus};
 
 /// funny unsafe wrapper
-fn get_invoker_target<'a, K, V>(
-    players: &'a mut HashMap<K, V>,
-    invoker: &K,
-    target: &K,
-) -> (&'a mut V, &'a mut V)
-where
-    K: Eq + std::hash::Hash,
-{
+fn get_invoker_target_next<'a>(
+    players: &'a mut HashMap<String, PlayerStatus>,
+    invoker: &String,
+    target: &String,
+) -> (&'a mut PlayerStatus, &'a mut PlayerStatus, String) {
     unsafe {
         // NOTE: soo... I don't really know the consequences of possibly
         // having two mutable references to the same value, but I guess
@@ 36,9 33,22 @@ where
         // if the wanted values were the same.
         // e.g. returning (V, None), if the keys were the same.
 
-        let invoker_ref = players.get_mut(invoker).unwrap() as *mut _;
-        let target_ref = players.get_mut(target).unwrap() as *mut _;
-        (&mut *invoker_ref, &mut *target_ref)
+        let invoker_ref: *mut PlayerStatus = players.get_mut(invoker).unwrap() as *mut _;
+        let target_ref: *mut PlayerStatus = players.get_mut(target).unwrap() as *mut _;
+
+        let next_turn_n: usize = if ((*invoker_ref).turn_n + 1) > (players.len() - 1)
+        {
+            0
+        } else {
+            (*invoker_ref).turn_n + 1
+        };
+
+        let next_player = players
+            .iter()
+            .find(|np| np.1.turn_n == next_turn_n)
+            .unwrap();
+
+        (&mut *invoker_ref, &mut *target_ref, next_player.0.clone())
     }
 }
 
@@ 86,6 96,9 @@ pub fn parse(game: &Game) -> Result<GameStatus, ()> {
         parse_action(&action, game, &mut game_status);
     }
 
+    // TODO: check for end conditions, declare one player as winner.
+    // update game state in API to ended, set ended date (in API).
+
     Ok(game_status)
 }
 
@@ 100,8 113,8 @@ fn parse_action(action: &Action, game: &Game, game_status: &mut GameStatus) {
 
     // invoker: the one who invoked the action
     // target: the one who the action affects, may also be the invoker, e.g. draw
-    let (invoker, target) =
-        get_invoker_target(&mut game_status.players, &action.invoker, &action.target);
+    let (invoker, target, next_player_uuid) =
+        get_invoker_target_next(&mut game_status.players, &action.invoker, &action.target);
 
     let Some(action_pos) = game_status.actions.iter().position(|a| *a == action.clone()) else {
         panic!("Action was not found in game_status.actions!");
@@ 148,13 161,22 @@ fn parse_action(action: &Action, game: &Game, game_status: &mut GameStatus) {
             target.plays -= 1;
 
             let card = target.hand.remove(*index);
-            target.discard.push(card.clone());
+            if card.to_be_trashed {
+                // marked for trash, let it fall into oblivion
+            } else {
+                // discard normally
+                target.discard.push(card.clone());
+            }
 
             for card_action in &card.actions {
                 let action = &Action::new(
                     &game.id,
                     &action.invoker,
-                    &action.target,
+                    if card_action.target_self {
+                        &action.invoker
+                    } else {
+                        &next_player_uuid
+                    },
                     &card_action.command,
                     current_seed!(action),
                 );
@@ 218,7 240,7 @@ fn parse_action(action: &Action, game: &Game, game_status: &mut GameStatus) {
                 &game.id,
                 &action.target,
                 &action.target,
-                &Command::Draw { amount: 4 },
+                &Command::Draw { amount: 2 },
                 current_seed!(action),
             );
             game_status
@@ 236,6 258,26 @@ fn parse_action(action: &Action, game: &Game, game_status: &mut GameStatus) {
                     Rng::with_seed(action.seed.parse::<u64>().unwrap()).usize(1..=*sides);
             }
         }
+        Command::GivePlays { amount } => {
+            target.plays += amount;
+        }
+        Command::GiveBuys { amount } => {
+            target.buys += amount;
+        }
+        Command::GiveVP { amount } => {
+            target.vp += amount;
+        }
+        Command::MarkCardInDeckToBeTrashed { index } => {
+            match index {
+                Some(index) => {
+                    target.deck.get_mut(*index).unwrap().to_be_trashed = true;
+                }
+                None => {
+                    let deck_len = target.deck.len();
+                    target.deck.get_mut(Rng::with_seed(action.seed.parse::<u64>().unwrap()).usize(0..deck_len)).unwrap().to_be_trashed = true;
+                }
+            }
+        }
         #[allow(unreachable_patterns)]
         _ => todo!(),
     }
 
M client/src/plugins/game/card/mod.rs => client/src/plugins/game/card/mod.rs +1 -0
@@ 56,6 56,7 @@ impl Default for VisualCard {
                 long_details: vec![],
                 cost: 0,
                 actions: vec![],
+                to_be_trashed: false,
             },
         }
     }
 
M client/src/plugins/game/ui/state_button.rs => client/src/plugins/game/ui/state_button.rs +3 -14
@@ 12,7 12,7 @@ use crate::{
     api::game::{Action, Command},
     game_status::PlayerState,
     plugins::{GameActionCreateCallEvent, GameData},
-    AppState, Global,
+    AppState, Global, util::get_next_player,
 };
 
 pub struct StateButtonPlugin;
@@ 162,24 162,13 @@ fn interact_state_button(
                         });
                     }
                     PlayerState::BuyPhase => {
-                        let next_turn_n: usize = if (player.turn_n + 1) > (status.players.len() - 1)
-                        {
-                            0
-                        } else {
-                            player.turn_n + 1
-                        };
-
-                        let next_player = status
-                            .players
-                            .iter()
-                            .find(|np| np.1.turn_n == next_turn_n)
-                            .unwrap();
+                        let next_player = get_next_player(player, &status);
 
                         gac_ev_w.send(GameActionCreateCallEvent {
                             action: Action::new(
                                 &game.id,
                                 &user.id,
-                                next_player.0,
+                                &next_player.0,
                                 &Command::EndTurn {},
                                 None,
                             ),
 
M client/src/util/mod.rs => client/src/util/mod.rs +17 -0
@@ 10,3 10,20 @@ pub mod egui;
 
 mod action_to_log;
 pub use action_to_log::action_to_log;
+
+use crate::game_status::{GameStatus, PlayerStatus};
+
+pub fn get_next_player<'a>(player: &'a PlayerStatus, game_status: &'a GameStatus) -> (&'a String, &'a PlayerStatus) {
+    let next_turn_n: usize = if (player.turn_n + 1) > (game_status.players.len() - 1)
+    {
+        0
+    } else {
+        player.turn_n + 1
+    };
+
+    game_status
+        .players
+        .iter()
+        .find(|np| np.1.turn_n == next_turn_n)
+        .unwrap()
+}