diff --git a/core/src/avm1.rs b/core/src/avm1.rs index 3c1067ed4..d3f06e919 100644 --- a/core/src/avm1.rs +++ b/core/src/avm1.rs @@ -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) { 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); - } + 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), diff --git a/core/src/button.rs b/core/src/button.rs index acf86cbdd..24926f6e7 100644 --- a/core/src/button.rs +++ b/core/src/button.rs @@ -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; } } diff --git a/core/src/graphic.rs b/core/src/graphic.rs index 6b3aea5cf..b44326b5e 100644 --- a/core/src/graphic.rs +++ b/core/src/graphic.rs @@ -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 } diff --git a/core/src/movie_clip.rs b/core/src/movie_clip.rs index 09573039d..ecfc876ec 100644 --- a/core/src/movie_clip.rs +++ b/core/src/movie_clip.rs @@ -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, children: BTreeMap>, + variables: HashMap, } 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) { - self.goto_queue.push(frame); + 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,22 +359,15 @@ impl<'gc> DisplayObject<'gc> for MovieClip<'gc> { if child.read().hit_test(point) { return Some(*child); } - } - //} - 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; + let button = child.read().pick(point); + if button.is_some() { + return button; } } //} - false + None } fn as_movie_clip(&self) -> Option<&crate::movie_clip::MovieClip<'gc>> {