core: PlaceObjectAction::Replace swaps out graphic handles

A `PlaceObjectAction::Replace` signals that a shape should
be swapped with a different shape. Previously we instantiated a
completely new `Graphic`, but this is incorrect; instead the
underlying shape handle should be swapped out, but the outer object
remains. This is visible in AVM2 where you can access `Shape` as
a normal display object.
This commit is contained in:
Mike Welsh 2021-06-08 18:32:56 -07:00
parent 5acc476789
commit b1318ecb01
4 changed files with 46 additions and 49 deletions

View File

@ -1114,23 +1114,10 @@ pub trait TDisplayObject<'gc>:
}
}
fn copy_display_properties_from(
&self,
gc_context: MutationContext<'gc, '_>,
other: DisplayObject<'gc>,
) {
self.set_matrix(gc_context, &*other.matrix());
self.set_color_transform(gc_context, &*other.color_transform());
self.set_clip_depth(gc_context, other.clip_depth());
self.set_name(gc_context, &*other.name());
if let (Some(mut me), Some(other)) = (self.as_morph_shape(), other.as_morph_shape()) {
me.set_ratio(gc_context, other.ratio());
}
// onEnterFrame actions only apply to movie clips.
if let (Some(me), Some(other)) = (self.as_movie_clip(), other.as_movie_clip()) {
me.set_clip_actions(gc_context, other.clip_actions().iter().cloned().collect());
}
// TODO: More in here eventually.
/// Called when this object should be replaced by a PlaceObject tag.
#[allow(unused_variables)]
fn replace_with(&self, context: &mut UpdateContext<'_, 'gc, '_>, id: CharacterId) {
// Noop for most symbols; only shapes can replace their innards with another graphic.
}
fn object(&self) -> Avm1Value<'gc> {

View File

@ -146,6 +146,21 @@ impl<'gc> TDisplayObject<'gc> for Graphic<'gc> {
}
}
fn replace_with(&self, context: &mut UpdateContext<'_, 'gc, '_>, id: CharacterId) {
// Only a graphic can replace themselves with another graphic via a Replace PlaceObject tag.
// This does not create a new graphic instance, but instead swaps out the underlying handle to point to
// the new art.
if let Some(new_graphic) = context
.library
.library_for_movie_mut(self.movie().unwrap())
.get_graphic(id)
{
self.0.write(context.gc_context).static_data = new_graphic.0.read().static_data;
} else {
log::warn!("PlaceObject: expected graphic at character ID {}", id);
}
}
fn run_frame(&self, _context: &mut UpdateContext) {
// Noop
}

View File

@ -1092,14 +1092,12 @@ impl<'gc> MovieClip<'gc> {
}
/// Instantiate a given child object on the timeline at a given depth.
#[allow(clippy::too_many_arguments)]
fn instantiate_child(
self,
context: &mut UpdateContext<'_, 'gc, '_>,
id: CharacterId,
depth: Depth,
place_object: &swf::PlaceObject,
copy_previous_properties: bool,
) -> Option<DisplayObject<'gc>> {
match context
.library
@ -1122,11 +1120,7 @@ impl<'gc> MovieClip<'gc> {
} else {
child.set_place_frame(context.gc_context, self.current_frame());
}
if copy_previous_properties {
if let Some(prev_child) = prev_child {
child.copy_display_properties_from(context.gc_context, prev_child);
}
}
// Run first frame.
child.apply_place_object(context, self.movie(), place_object);
child.construct_frame(context);
@ -1317,7 +1311,6 @@ impl<'gc> MovieClip<'gc> {
params.id(),
params.depth(),
&params.place_object,
params.modifies_original_item(),
) {
// Set the place frame to the frame where the object *would* have been placed.
child.set_place_frame(context.gc_context, params.frame);
@ -3114,28 +3107,28 @@ impl<'gc, 'a> MovieClip<'gc> {
}?;
use swf::PlaceObjectAction;
match place_object.action {
PlaceObjectAction::Place(id) | PlaceObjectAction::Replace(id) => {
if let Some(child) = self.instantiate_child(
context,
id,
place_object.depth.into(),
&place_object,
matches!(place_object.action, PlaceObjectAction::Replace(_)),
) {
child
PlaceObjectAction::Place(id) => {
self.instantiate_child(context, id, place_object.depth.into(), &place_object);
}
PlaceObjectAction::Replace(id) => {
if let Some(child) = self.child_by_depth(place_object.depth.into()) {
child.replace_with(context, id);
child.apply_place_object(context, self.movie(), &place_object);
if child.avm_type() == AvmType::Avm2 {
// In AVM2 instantiation happens before frame advance so we
// have to special-case that
child.set_place_frame(context.gc_context, self.current_frame() + 1);
} else {
return Ok(());
child.set_place_frame(context.gc_context, self.current_frame());
}
}
}
PlaceObjectAction::Modify => {
if let Some(child) = self.child_by_depth(place_object.depth.into()) {
child.apply_place_object(context, self.movie(), &place_object);
child
} else {
return Ok(());
}
}
};
}
Ok(())
}
@ -3373,14 +3366,6 @@ impl<'a> GotoPlaceObject<'a> {
}
}
#[inline]
fn modifies_original_item(&self) -> bool {
matches!(
&self.place_object.action,
swf::PlaceObjectAction::Replace(_)
)
}
#[inline]
fn depth(&self) -> Depth {
self.place_object.depth.into()

View File

@ -3,7 +3,7 @@ use crate::avm1::property_map::PropertyMap as Avm1PropertyMap;
use crate::avm2::{Domain as Avm2Domain, Object as Avm2Object};
use crate::backend::audio::SoundHandle;
use crate::character::Character;
use crate::display_object::{Bitmap, TDisplayObject};
use crate::display_object::{Bitmap, Graphic, TDisplayObject};
use crate::font::{Font, FontDescriptor};
use crate::prelude::*;
use crate::tag_utils::SwfMovie;
@ -287,6 +287,16 @@ impl<'gc> MovieLibrary<'gc> {
self.fonts.get(&descriptor).copied()
}
/// Returns the graphic with the given character ID.
/// Returns `None` if the ID does not exist or is not a `Graphic`.
pub fn get_graphic(&self, id: CharacterId) -> Option<Graphic<'gc>> {
if let Some(&Character::Graphic(graphic)) = self.characters.get(&id) {
Some(graphic)
} else {
None
}
}
pub fn get_sound(&self, id: CharacterId) -> Option<SoundHandle> {
if let Some(Character::Sound(sound)) = self.characters.get(&id) {
Some(*sound)