avm1: Implement GetVariable/SetVariable

This commit is contained in:
Mike Welsh 2019-08-15 11:29:00 -07:00
parent ecd9b18e90
commit 20ec170552
4 changed files with 78 additions and 25 deletions

View File

@ -75,6 +75,7 @@ impl Avm1 {
Action::GetMember => self.action_get_member(context)?,
Action::GetProperty => self.action_get_property(context)?,
Action::GetTime => self.action_get_time(context)?,
Action::GetVariable => self.action_get_variable(context)?,
Action::GetUrl { url, target } => self.action_get_url(context, &url, &target)?,
Action::GetUrl2 {
send_vars_method,
@ -120,6 +121,7 @@ impl Avm1 {
Action::Return => self.action_return(context)?,
Action::SetMember => self.action_set_member(context)?,
Action::SetTarget(target) => self.action_set_target(context, &target)?,
Action::SetVariable => self.action_set_variable(context)?,
Action::StackSwap => self.action_stack_swap(context)?,
Action::StartDrag => self.action_start_drag(context)?,
Action::Stop => self.action_stop(context)?,
@ -183,6 +185,25 @@ impl Avm1 {
Some(cur_clip)
}
pub fn resolve_slash_path_variable<'gc, 's>(
start: DisplayNode<'gc>,
root: DisplayNode<'gc>,
path: &'s str,
) -> Option<(DisplayNode<'gc>, &'s str)> {
if !path.is_empty() {
let mut var_iter = path.splitn(2, ':');
match (var_iter.next(), var_iter.next()) {
(Some(var_name), None) => return Some((start, var_name)),
(Some(path), Some(var_name)) => if let Some(node) = Self::resolve_slash_path(start, root, path) {
return Some((node, var_name));
}
_ => (),
}
}
None
}
fn push(&mut self, value: impl Into<Value>) {
self.stack.push(value.into());
}
@ -476,6 +497,19 @@ impl Avm1 {
Ok(())
}
fn action_get_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
// Flash 4-style variable
let var_path = self.pop()?;
if let Some((node, var_name)) = Self::resolve_slash_path_variable(context.active_clip, context.root, var_path.as_string()?) {
if let Some(clip) = node.read().as_movie_clip() {
self.push(clip.get_variable(var_name));
}
} else {
self.push(Value::Undefined);
}
Ok(())
}
fn action_get_url(
&mut self,
_context: &mut ActionContext,
@ -504,11 +538,7 @@ impl Avm1 {
fn action_goto_frame(&mut self, context: &mut ActionContext, frame: u16) -> Result<(), Error> {
let mut display_object = context.active_clip.write(context.gc_context);
let clip = display_object.as_movie_clip_mut().unwrap();
if clip.playing() {
clip.goto_frame(frame + 1, false);
} else {
clip.goto_frame(frame + 1, true);
}
Ok(())
}
@ -798,6 +828,18 @@ impl Avm1 {
unimplemented!("Action::SetMember");
}
fn action_set_variable(&mut self, context: &mut ActionContext) -> Result<(), Error> {
// Flash 4-style variable
let value = self.pop()?;
let var_path = self.pop()?;
if let Some((node, var_name)) = Self::resolve_slash_path_variable(context.active_clip, context.root, var_path.as_string()?) {
if let Some(clip) = node.write(context.gc_context).as_movie_clip_mut() {
clip.set_variable(var_name, value);
}
}
Ok(())
}
fn action_set_target(
&mut self,
context: &mut ActionContext,
@ -809,6 +851,9 @@ impl Avm1 {
Avm1::resolve_slash_path(context.start_clip, context.root, target)
{
context.active_clip = clip;
} else {
log::warn!("SetTarget failed: {} not found", target);
// TODO: Do we change active_clip to something? Undefined?
}
Ok(())
}
@ -1008,7 +1053,7 @@ type ObjectPtr = std::marker::PhantomData<()>;
#[derive(Debug, Clone)]
#[allow(dead_code)]
enum Value {
pub enum Value {
Undefined,
Null,
Bool(bool),

View File

@ -52,7 +52,9 @@ impl<'gc> Button<'gc> {
for actions in &button.actions {
if actions
.conditions
.contains(&swf::ButtonActionCondition::OverDownToOverUp)
.contains(&swf::ButtonActionCondition::OverDownToOverUp) || actions
.conditions
.contains(&swf::ButtonActionCondition::OverUpToOverDown)
{
release_actions = actions.action_data.clone();
}
@ -123,7 +125,7 @@ impl<'gc> DisplayObject<'gc> for Button<'gc> {
fn hit_test(&self, point: (Twips, Twips)) -> bool {
//if self.world_bounds().contains(point) {
for child in self.children_in_state(self.state).rev() {
if child.read().hit_test(point) {
if child.read().world_bounds().contains(point) {
return true;
}
}

View File

@ -41,10 +41,6 @@ impl<'gc> DisplayObject<'gc> for Graphic<'gc> {
bounds
}
fn hit_test(&self, point: (Twips, Twips)) -> bool {
self.world_bounds().contains(point)
}
fn run_frame(&mut self, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1,3 +1,4 @@
use crate::avm1;
use crate::backend::audio::AudioStreamHandle;
use crate::character::Character;
use crate::color_transform::ColorTransform;
@ -10,7 +11,7 @@ use crate::player::{RenderContext, UpdateContext};
use crate::prelude::*;
use crate::tag_utils::{self, DecodeResult, SwfStream};
use crate::text::Text;
use std::collections::BTreeMap;
use std::collections::{BTreeMap, HashMap};
use swf::read::SwfRead;
type Depth = i16;
@ -32,6 +33,7 @@ pub struct MovieClip<'gc> {
audio_stream: Option<AudioStreamHandle>,
children: BTreeMap<Depth, DisplayNode<'gc>>,
variables: HashMap<String, avm1::Value>,
}
impl<'gc> MovieClip<'gc> {
@ -49,6 +51,7 @@ impl<'gc> MovieClip<'gc> {
audio_stream: None,
audio_stream_info: None,
children: BTreeMap::new(),
variables: HashMap::new(),
}
}
@ -71,6 +74,7 @@ impl<'gc> MovieClip<'gc> {
audio_stream_info: None,
total_frames: num_frames,
children: BTreeMap::new(),
variables: HashMap::new(),
}
}
@ -99,7 +103,9 @@ impl<'gc> MovieClip<'gc> {
}
pub fn goto_frame(&mut self, frame: FrameNumber, stop: bool) {
if frame != self.current_frame {
self.goto_queue.push(frame);
}
if stop {
self.stop();
@ -138,6 +144,7 @@ impl<'gc> MovieClip<'gc> {
}
pub fn get_child_by_name(&self, name: &str) -> Option<&DisplayNode<'gc>> {
// TODO: Make a HashMap from name -> child?
self.children
.values()
.find(|child| child.read().name() == name)
@ -195,6 +202,16 @@ impl<'gc> MovieClip<'gc> {
self.goto_queue.clear();
}
pub fn get_variable(&self, var_name: &str) -> avm1::Value {
// TODO: Value should be Copy (and contain a Cow/GcCell for big objects)
self.variables.get(var_name).unwrap_or(&avm1::Value::Undefined).clone()
}
pub fn set_variable(&mut self, var_name: &str, value: avm1::Value) {
// TODO: Cow for String values.
self.variables.insert(var_name.to_owned(), value);
}
fn reader<'a>(
&self,
context: &UpdateContext<'a, '_, '_>,
@ -342,24 +359,17 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> {
if child.read().hit_test(point) {
return Some(*child);
}
let button = child.read().pick(point);
if button.is_some() {
return button;
}
}
//}
None
}
fn hit_test(&self, point: (Twips, Twips)) -> bool {
//if self.world_bounds().contains(point) {
for child in self.children.values().rev() {
if child.read().hit_test(point) {
return true;
}
}
//}
false
}
fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip<'gc>> {
Some(self)
}