Skip to content

Commit

Permalink
Add imgul_on / imgol_on
Browse files Browse the repository at this point in the history
  • Loading branch information
neivv committed Aug 20, 2021
1 parent ad32edd commit f6710db
Show file tree
Hide file tree
Showing 6 changed files with 214 additions and 3 deletions.
16 changes: 16 additions & 0 deletions Documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ Calls a subroutine. Use `return` inside subroutine to continue from a point afte
Unlike `call` in vanilla BW, which only allows a single call that must be returned from before
using `call` again, Aice allows nested calls up to 256 calls.

### imgul\_on / imgol\_on

```
imgul_on <unit> <image_id> <x> <y>
imgol_on <unit> <image_id> <x> <y>
```

Versions of `imgul` and `imgol` which spawn the overlay on [another unit][other-units].
Additionally the image ID, x and y are can be any [expression][expr].

Note that as a minor difference from regular `imgul` and `imgol`, which insert the overlay
directly below or above the current image executing the command, `imgul_on` and `imgol_on`
will spawn the overlay as the top/bottom overlay of the entire sprite.

If the unit does not exist, this command will silently do nothing.

## Expressions

There are two types of expressions: Integer (32-bit signed) and boolean expressions
Expand Down
88 changes: 87 additions & 1 deletion src/iscript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use lazy_static::lazy_static;
use libc::c_void;
use serde_derive::{Serialize, Deserialize};

use bw_dat::{Game, OrderId, Unit, UnitId, UpgradeId, Sprite, TechId, Race};
use bw_dat::{Game, Image, ImageId, OrderId, Unit, UnitId, UpgradeId, Sprite, TechId, Race};

use crate::bw;
use crate::globals::{Globals, PlayerColorChoices, SerializableSprite, SerializableImage};
Expand Down Expand Up @@ -173,6 +173,12 @@ pub unsafe extern fn run_aice_script(
drop(this_guard);
bw::issue_order(*unit, order, pos, null_mut(), bw_dat::unit::NONE);
}
Ok(ScriptRunResult::AddOverlay(base, image_id, x, y, above)) => {
drop(globals_guard);
drop(sprite_owner_map);
drop(this_guard);
crate::samase::add_overlay_iscript(*base, image_id, x, y, above);
}
Err(pos) => {
invalid_aice_command(iscript, image, CodePos::Aice(pos));
return;
Expand Down Expand Up @@ -518,6 +524,7 @@ enum ScriptRunResult {
Done,
CreateUnit(UnitId, bw::Point, u8),
IssueOrder(Unit, OrderId, bw::Point),
AddOverlay(Image, ImageId, i8, i8, bool),
}

impl<'a> IscriptRunner<'a> {
Expand Down Expand Up @@ -1102,6 +1109,63 @@ impl<'a> IscriptRunner<'a> {
(*flingy).order_signal &= !flags;
}
}
IMG_ON => {
let flags = self.read_u8()?;
let unit_ref = UnitRefId(self.read_u16()?);
let image_id = match flags & 0x2 == 0 {
true => self.read_u32()?,
false => self.read_u16()? as u32,
};
let x = match flags & 0x4 == 0 {
true => self.read_u32()?,
false => self.read_u8()? as u32,
};
let y = match flags & 0x8 == 0 {
true => self.read_u32()?,
false => self.read_u8()? as u32,
};
if self.dry_run {
continue 'op_loop;
}
let sprite = match self.resolve_unit_ref(unit_ref).and_then(|x| x.sprite()) {
Some(s) => s,
None => continue 'op_loop,
};
let iscript = self.iscript;
let mut eval_ctx = self.eval_ctx();
let image_id = match flags & 0x2 == 0 {
true => {
let expr = &iscript.int_expressions[image_id as usize];
clamp_i32_u16(eval_ctx.eval_int(expr))
}
false => image_id as u16,
};
let image_id = ImageId(image_id);
let x = match flags & 0x4 == 0 {
true => {
let expr = &iscript.int_expressions[x as usize];
clamp_i32_iu8(eval_ctx.eval_int(expr))
}
false => x as i8,
};
let y = match flags & 0x8 == 0 {
true => {
let expr = &iscript.int_expressions[y as usize];
clamp_i32_iu8(eval_ctx.eval_int(expr))
}
false => y as i8,
};
let above = flags & 0x1 != 0;
let base_image = match above {
true => sprite.images().next(),
// Ugh but lazy and pretty minor in the end
false => sprite.images().last(),
};

if let Some(base_image) = base_image {
return Ok(ScriptRunResult::AddOverlay(base_image, image_id, x, y, above));
}
}
x => {
error!("Unknown opcode {:02x}", x);
return Err(opcode_pos);
Expand Down Expand Up @@ -1355,6 +1419,28 @@ fn clamp_i32_u16(val: i32) -> u16 {
val.max(0).min(i32::from(u16::MAX)) as u16
}

fn clamp_i32_iu8(val: i32) -> i8 {
if val < 0 {
val.max(i8::MIN.into()) as i8
} else {
val.min(u8::MAX.into()) as u8 as i8
}
}

#[test]
fn test_clamps() {
assert_eq!(clamp_i32_u16(-1), 0);
assert_eq!(clamp_i32_u16(-130), 0);
assert_eq!(clamp_i32_u16(130), 130);
assert_eq!(clamp_i32_u16(69094), 65535);
assert_eq!(clamp_i32_iu8(-1), -1);
assert_eq!(clamp_i32_iu8(-130), -128);
assert_eq!(clamp_i32_iu8(130), 130u8 as i8);
assert_eq!(clamp_i32_iu8(126), 126);
assert_eq!(clamp_i32_iu8(127), 127);
assert_eq!(clamp_i32_iu8(500), 255u8 as i8);
}

unsafe fn image_play_frame(image: *mut bw::Image, frameset: u16) -> Result<(), u16> {
if (*image).drawfunc == 0xb {
// HP bar, does not have a concept of frame and grp pointer isn't same struct.
Expand Down
94 changes: 93 additions & 1 deletion src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ pub mod aice_op {
pub const SIGORDER: u8 = 0x0c;
pub const ORDER_DONE: u8 = 0x0d;
pub const ISSUE_ORDER: u8 = 0x0e;
pub const IMG_ON: u8 = 0x0f;
}

quick_error! {
Expand Down Expand Up @@ -502,6 +503,8 @@ static COMMANDS: &[(&[u8], CommandPrototype)] = {
(b"fireweapon", FireWeapon),
(b"create_unit", CreateUnit),
(b"issue_order", IssueOrder),
(b"imgul_on", ImgulOn),
(b"imgol_on", ImgolOn),
]
};

Expand Down Expand Up @@ -660,6 +663,8 @@ enum CommandPrototype {
PlayFram,
Sigorder,
OrderDone,
ImgulOn,
ImgolOn,
}

pub struct Iscript {
Expand Down Expand Up @@ -1288,11 +1293,88 @@ impl<'a> Parser<'a> {
compiler.add_set(&mut ctx.write_buffer, place, expr, &mut ctx.expr_buffer);
Ok(())
}
CommandPrototype::ImgulOn | CommandPrototype::ImgolOn => {
// op, flags, target, image, x, y
// Flags 0x1 = Imgol, 0x2 = image is u16 const, 0x4 = x is i8 const
// 0x8 = y is i8 const
// Otherwise u32 expressions
let (unit_ref, rest) = self.parse_unit_ref_rest(rest)?;
let (image, rest) = parse_int_expr(rest, self, &compiler)?;
let (x, rest) = parse_int_expr(rest, self, &compiler)?;
let (y, rest) = parse_int_expr(rest, self, &compiler)?;
if !rest.is_empty() {
return Err(
Error::Dynamic(format!("Trailing characters '{}'", rest.as_bstr()))
);
}

let mut flags = match command {
CommandPrototype::ImgulOn => 0,
_ => 0x1,
};
let image = if let Some(c) = is_constant_expr(&image) {
flags |= 0x2;
u16::try_from(c)
.map(|x| x as u32)
.map_err(|_| Error::Msg("Image ID must be between 0 and 65535"))?
} else {
compiler.int_expr_id(image)
};
let x = if let Some(c) = is_constant_expr(&x) {
flags |= 0x4;
u8::try_from(c)
.map(|x| x as u32)
.or_else(|_| i8::try_from(c).map(|x| x as u32))
.map_err(|_| Error::Msg("X must be between -128 and 255"))?
} else {
compiler.int_expr_id(x)
};
let y = if let Some(c) = is_constant_expr(&y) {
flags |= 0x8;
u8::try_from(c)
.map(|x| x as u32)
.or_else(|_| i8::try_from(c).map(|x| x as u32))
.map_err(|_| Error::Msg("Y must be between -128 and 255"))?
} else {
compiler.int_expr_id(y)
};

compiler.add_aice_command(aice_op::IMG_ON);
compiler.aice_bytecode.write_u8(flags).unwrap();
compiler.aice_bytecode.write_u16::<LE>(unit_ref.0).unwrap();
if flags & 0x2 == 0 {
compiler.aice_bytecode.write_u32::<LE>(image).unwrap();
} else {
compiler.aice_bytecode.write_u16::<LE>(image as u16).unwrap();
}
if flags & 0x4 == 0 {
compiler.aice_bytecode.write_u32::<LE>(x).unwrap();
} else {
compiler.aice_bytecode.write_u8(x as u8).unwrap();
}
if flags & 0x8 == 0 {
compiler.aice_bytecode.write_u32::<LE>(y).unwrap();
} else {
compiler.aice_bytecode.write_u8(y as u8).unwrap();
}
Ok(())
}
}
_ => Ok(())
}
}

fn parse_unit_ref_rest<'text>(
&mut self,
text: &'text [u8],
) -> Result<(UnitRefId, &'text [u8]), Error> {
let space = text.iter().position(|&x| x == b' ' || x == b'\t')
.unwrap_or(text.len());
let (text, rest) = text.split_at(space);
let val = self.unit_refs.parse(text)?;
Ok((val, &rest[1..]))
}

/// If `variables` is set, parses place variables `game.deaths(a, b)`, to there.
/// Otherwise just skips over them.
fn parse_set_place(
Expand Down Expand Up @@ -1418,6 +1500,14 @@ impl<'a> Parser<'a> {
}
}

fn is_constant_expr(expr: &IntExpr) -> Option<i32> {
if let IntExprTree::Integer(c) = *expr.inner() {
Some(c)
} else {
None
}
}

struct TextParseContext<'a> {
// Block, index, block start line
block_stack: Vec<(Block<'a>, BlockId, usize)>,
Expand Down Expand Up @@ -1773,6 +1863,7 @@ pub enum UnitVar {
BuildQueue,
RemainingBuildTime,
RemainingResearchTime,
OverlaySize,
}

#[repr(u8)]
Expand Down Expand Up @@ -1869,7 +1960,7 @@ pub enum Place {
/// Anything below `UnitObject::_Last` is just that without extra projection,
/// otherwise iscript.unit_refs[x - UnitObject::_Last]
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
pub struct UnitRefId(u16);
pub struct UnitRefId(pub u16);

impl UnitRefId {
pub fn this() -> UnitRefId {
Expand Down Expand Up @@ -3077,6 +3168,7 @@ mod test {
find_error(&mut errors, "needs `default`", 61);
find_error(&mut errors, "needs `default`", 62);
find_error(&mut errors, "needs `default`", 63);
find_error(&mut errors, "X must be between", 78);
assert!(errors.is_empty());
}
}
17 changes: 16 additions & 1 deletion src/samase.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use libc::c_void;
use winapi::um::processthreadsapi::{GetCurrentProcess, TerminateProcess};

use bw_dat::{OrderId, UnitId};
use bw_dat::{OrderId, UnitId, ImageId};
use samase_shim::PluginApi;

use crate::bw;
Expand Down Expand Up @@ -196,6 +196,20 @@ pub fn issue_order(
unsafe { ISSUE_ORDER.get()(unit, order.0 as u32, x, y, target, fow_unit.0 as u32) }
}

static mut ADD_OVERLAY_ISCRIPT: GlobalFunc<
unsafe extern fn(*mut bw::Image, u32, i32, i32, u32),
> = GlobalFunc(None);

pub unsafe fn add_overlay_iscript(
base_image: *mut bw::Image,
image: ImageId,
x: i8,
y: i8,
above: bool,
) {
ADD_OVERLAY_ISCRIPT.get()(base_image, image.0 as u32, x as i32, y as i32, above as u32)
}

pub struct SamaseBox {
data: NonNull<u8>,
len: usize,
Expand Down Expand Up @@ -302,6 +316,7 @@ pub unsafe extern fn samase_plugin_init(api: *const PluginApi) {
IS_MULTIPLAYER.0 = Some(mem::transmute(((*api).is_multiplayer)()));
UNIT_ARRAY_LEN.0 = Some(mem::transmute(((*api).unit_array_len)()));
ISSUE_ORDER.0 = Some(mem::transmute(((*api).issue_order)()));
ADD_OVERLAY_ISCRIPT.0 = Some(mem::transmute(((*api).add_overlay_iscript)()));
let result = ((*api).extend_save)(
"aice\0".as_ptr(),
Some(crate::globals::save),
Expand Down
1 change: 1 addition & 0 deletions test_scripts/unit_refs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,6 @@ ScourgeLocal00:
goto ScourgeLocal00

long00:
imgol_on unit.target 10 0 0
wait 125
goto long00
1 change: 1 addition & 0 deletions test_scripts/unit_refs_err.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,6 @@ ScourgeLocal00:
goto ScourgeLocal00

long00:
imgol_on unit.target 10 299 0
wait 125
goto long00

0 comments on commit f6710db

Please sign in to comment.