Implement the first bits of bytecode processing, including the ability to go to an animation frame.
This commit is contained in:
parent
0a6635d993
commit
14c8ac9347
@ -1,9 +1,10 @@
|
||||
from typing import Dict, List, Tuple, Optional, Union
|
||||
from typing import Any, Dict, List, Tuple, Optional, Union
|
||||
from PIL import Image # type: ignore
|
||||
|
||||
from .blend import affine_composite
|
||||
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
|
||||
from .types import Color, Matrix, Point, Rectangle
|
||||
from .decompile import ByteCode
|
||||
from .types import Color, Matrix, Point, Rectangle, AP2Trigger, AP2Action, PushAction, StoreRegisterAction, StringConstant, Register, THIS, UNDEFINED
|
||||
from .geo import Shape, DrawParams
|
||||
from .util import VerboseOutput
|
||||
|
||||
@ -47,7 +48,7 @@ class RegisteredDummy:
|
||||
|
||||
class PlacedObject:
|
||||
# An object that occupies the screen at some depth.
|
||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int) -> None:
|
||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, mask: Optional[Rectangle]) -> None:
|
||||
self.__object_id = object_id
|
||||
self.__depth = depth
|
||||
self.rotation_offset = rotation_offset
|
||||
@ -55,6 +56,7 @@ class PlacedObject:
|
||||
self.mult_color = mult_color
|
||||
self.add_color = add_color
|
||||
self.blend = blend
|
||||
self.mask = mask
|
||||
|
||||
@property
|
||||
def source(self) -> Union[RegisteredClip, RegisteredShape, RegisteredDummy]:
|
||||
@ -75,8 +77,19 @@ class PlacedObject:
|
||||
class PlacedShape(PlacedObject):
|
||||
# A shape that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag
|
||||
# referencing an AP2ShapeTag.
|
||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredShape) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend)
|
||||
def __init__(
|
||||
self,
|
||||
object_id: int,
|
||||
depth: int,
|
||||
rotation_offset: Point,
|
||||
transform: Matrix,
|
||||
mult_color: Color,
|
||||
add_color: Color,
|
||||
blend: int,
|
||||
mask: Optional[Rectangle],
|
||||
source: RegisteredShape,
|
||||
) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||
self.__source = source
|
||||
|
||||
@property
|
||||
@ -90,11 +103,24 @@ class PlacedShape(PlacedObject):
|
||||
class PlacedClip(PlacedObject):
|
||||
# A movieclip that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag
|
||||
# referencing an AP2DefineSpriteTag. Essentially an embedded movie clip.
|
||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredClip) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend)
|
||||
def __init__(
|
||||
self,
|
||||
object_id: int,
|
||||
depth: int,
|
||||
rotation_offset: Point,
|
||||
transform: Matrix,
|
||||
mult_color: Color,
|
||||
add_color: Color,
|
||||
blend: int,
|
||||
mask: Optional[Rectangle],
|
||||
source: RegisteredClip,
|
||||
) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||
self.placed_objects: List[PlacedObject] = []
|
||||
self.frame: int = 0
|
||||
self.__source = source
|
||||
self.playing: bool = True
|
||||
self.requested_frame: Optional[int] = None
|
||||
|
||||
@property
|
||||
def source(self) -> RegisteredClip:
|
||||
@ -111,11 +137,49 @@ class PlacedClip(PlacedObject):
|
||||
def __repr__(self) -> str:
|
||||
return f"PlacedClip(object_id={self.object_id}, depth={self.depth}, source={self.source}, frame={self.frame}, total_frames={len(self.source.frames)}, finished={self.finished})"
|
||||
|
||||
# The following are attributes and functions necessary to support some simple bytecode.
|
||||
def gotoAndPlay(self, frame: Any) -> None:
|
||||
if not isinstance(frame, int):
|
||||
# TODO: Technically this should also allow string labels to frames as identified in the
|
||||
# SWF specification, but we don't support that here.
|
||||
print(f"WARNING: Non-integer frame {frame} to gotoAndPlay function!")
|
||||
return
|
||||
if frame <= 0 or frame > len(self.source.frames):
|
||||
return
|
||||
self.requested_frame = frame
|
||||
self.playing = True
|
||||
|
||||
@property
|
||||
def frameOffset(self) -> int:
|
||||
return self.requested_frame or self.frame
|
||||
|
||||
@frameOffset.setter
|
||||
def frameOffset(self, val: Any) -> None:
|
||||
if not isinstance(val, int):
|
||||
# TODO: Technically this should also allow string labels to frames as identified in the
|
||||
# SWF specification, but we don't support that here.
|
||||
print(f"WARNING: Non-integer frameOffset {val} to frameOffset attribute!")
|
||||
return
|
||||
if val < 0 or val >= len(self.source.frames):
|
||||
return
|
||||
self.requested_frame = val + 1
|
||||
|
||||
|
||||
class PlacedDummy(PlacedObject):
|
||||
# A reference to an object we can't find because we're missing the import.
|
||||
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredDummy) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend)
|
||||
def __init__(
|
||||
self,
|
||||
object_id: int,
|
||||
depth: int,
|
||||
rotation_offset: Point,
|
||||
transform: Matrix,
|
||||
mult_color: Color,
|
||||
add_color: Color,
|
||||
blend: int,
|
||||
mask: Optional[Rectangle],
|
||||
source: RegisteredDummy,
|
||||
) -> None:
|
||||
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
|
||||
self.__source = source
|
||||
|
||||
@property
|
||||
@ -123,6 +187,50 @@ class PlacedDummy(PlacedObject):
|
||||
return self.__source
|
||||
|
||||
|
||||
class Movie:
|
||||
def __init__(self, root: PlacedClip) -> None:
|
||||
self.__root = root
|
||||
|
||||
def getInstanceAtDepth(self, depth: Any) -> Any:
|
||||
if not isinstance(depth, int):
|
||||
return UNDEFINED
|
||||
|
||||
# For some reason, it looks like internally the depth of all objects is
|
||||
# stored added to -0x4000, so let's reverse that.
|
||||
depth = depth + 0x4000
|
||||
|
||||
for obj in self.__root.placed_objects:
|
||||
if obj.depth == depth:
|
||||
return obj
|
||||
|
||||
print(f"WARNING: Could not find object at depth {depth}!")
|
||||
return UNDEFINED
|
||||
|
||||
|
||||
class AEPLib:
|
||||
def __init__(self, this: PlacedObject, movie: Movie) -> None:
|
||||
self.__this = this
|
||||
self.__movie = movie
|
||||
|
||||
def aep_set_rect_mask(self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any) -> None:
|
||||
if not isinstance(left, (int, float)) or not isinstance(right, (int, float)) or not isinstance(top, (int, float)) or not isinstance(bottom, (int, float)):
|
||||
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters!")
|
||||
return
|
||||
if thisptr is THIS:
|
||||
self.__this.mask = Rectangle(
|
||||
left=float(left),
|
||||
right=float(right),
|
||||
top=float(top),
|
||||
bottom=float(bottom),
|
||||
)
|
||||
else:
|
||||
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target!")
|
||||
|
||||
def aep_set_set_frame(self, thisptr: Any, frame: Any) -> None:
|
||||
# I have no idea what this should do, so let's ignore it.
|
||||
pass
|
||||
|
||||
|
||||
class AFPRenderer(VerboseOutput):
|
||||
def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False) -> None:
|
||||
super().__init__()
|
||||
@ -137,6 +245,7 @@ class AFPRenderer(VerboseOutput):
|
||||
|
||||
# Internal render parameters.
|
||||
self.__registered_objects: Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredDummy]] = {}
|
||||
self.__movie: Optional[Movie] = None
|
||||
|
||||
def add_shape(self, name: str, data: Shape) -> None:
|
||||
# Register a named shape with the renderer.
|
||||
@ -193,6 +302,119 @@ class AFPRenderer(VerboseOutput):
|
||||
|
||||
return paths
|
||||
|
||||
def __execute_bytecode(self, bytecode: ByteCode, clip: PlacedClip) -> None:
|
||||
if self.__movie is None:
|
||||
raise Exception("Logic error, executing bytecode outside of a rendering movie clip!")
|
||||
|
||||
location: int = 0
|
||||
stack: List[Any] = []
|
||||
variables: Dict[str, Any] = {
|
||||
'aeplib': AEPLib(clip, self.__movie),
|
||||
'GLOBAL': self.__movie,
|
||||
}
|
||||
registers: List[Any] = [UNDEFINED] * 256
|
||||
|
||||
while location < len(bytecode.actions):
|
||||
action = bytecode.actions[location]
|
||||
|
||||
if action.opcode == AP2Action.END:
|
||||
# End the execution.
|
||||
self.vprint("Ending bytecode execution.")
|
||||
break
|
||||
elif action.opcode == AP2Action.GET_VARIABLE:
|
||||
varname = stack.pop()
|
||||
|
||||
# Look up the variable, put it on the stack.
|
||||
if varname in variables:
|
||||
stack.append(variables[varname])
|
||||
else:
|
||||
stack.append(UNDEFINED)
|
||||
elif action.opcode == AP2Action.SET_MEMBER:
|
||||
# Grab what we're about to do.
|
||||
set_value = stack.pop()
|
||||
attribute = stack.pop()
|
||||
obj = stack.pop()
|
||||
|
||||
if not hasattr(obj, attribute):
|
||||
print(f"WARNING: Tried to set attribute {attribute} on {obj} but that attribute doesn't exist!")
|
||||
else:
|
||||
setattr(obj, attribute, set_value)
|
||||
elif action.opcode == AP2Action.CALL_METHOD:
|
||||
# Grab the method name.
|
||||
methname = stack.pop()
|
||||
|
||||
# Grab the object to perform the call on.
|
||||
obj = stack.pop()
|
||||
|
||||
# Grab the parameters to pass to the function.
|
||||
num_params = stack.pop()
|
||||
if not isinstance(num_params, int):
|
||||
raise Exception("Logic error, cannot get number of parameters to method call!")
|
||||
params = []
|
||||
for _ in range(num_params):
|
||||
params.append(stack.pop())
|
||||
|
||||
# Look up the python function we're calling.
|
||||
try:
|
||||
meth = getattr(obj, methname)
|
||||
|
||||
# Call it, set the return on the stack.
|
||||
stack.append(meth(*params))
|
||||
except AttributeError:
|
||||
# Function does not exist!
|
||||
print(f"WARNING: Tried to call {methname}({', '.join(repr(s) for s in params)}) on {obj} but that method doesn't exist!")
|
||||
stack.append(UNDEFINED)
|
||||
elif action.opcode == AP2Action.CALL_FUNCTION:
|
||||
# Grab the method name.
|
||||
funcname = stack.pop()
|
||||
|
||||
# Grab the object to perform the call on.
|
||||
obj = variables['GLOBAL']
|
||||
|
||||
# Grab the parameters to pass to the function.
|
||||
num_params = stack.pop()
|
||||
if not isinstance(num_params, int):
|
||||
raise Exception("Logic error, cannot get number of parameters to function call!")
|
||||
params = []
|
||||
for _ in range(num_params):
|
||||
params.append(stack.pop())
|
||||
|
||||
# Look up the python function we're calling.
|
||||
try:
|
||||
func = getattr(obj, funcname)
|
||||
|
||||
# Call it, set the return on the stack.
|
||||
stack.append(func(*params))
|
||||
except AttributeError:
|
||||
# Function does not exist!
|
||||
print(f"WARNING: Tried to call {funcname}({', '.join(repr(s) for s in params)}) on {obj} but that function doesn't exist!")
|
||||
stack.append(UNDEFINED)
|
||||
elif isinstance(action, PushAction):
|
||||
for obj in action.objects:
|
||||
if isinstance(obj, Register):
|
||||
stack.append(registers[obj.no])
|
||||
elif isinstance(obj, StringConstant):
|
||||
if obj.alias:
|
||||
stack.append(obj.alias)
|
||||
else:
|
||||
stack.append(StringConstant.property_to_name(obj.const))
|
||||
else:
|
||||
stack.append(obj)
|
||||
elif isinstance(action, StoreRegisterAction):
|
||||
set_value = stack.pop()
|
||||
if action.preserve_stack:
|
||||
stack.append(set_value)
|
||||
|
||||
for reg in action.registers:
|
||||
registers[reg.no] = set_value
|
||||
elif action.opcode == AP2Action.POP:
|
||||
stack.pop()
|
||||
else:
|
||||
print(f"WARNING: Unhandled opcode {action} with stack {stack}")
|
||||
|
||||
# Next opcode!
|
||||
location += 1
|
||||
|
||||
def __place(self, tag: Tag, operating_clip: PlacedClip, prefix: str = "") -> Tuple[Optional[PlacedClip], bool]:
|
||||
# "Place" a tag on the screen. Most of the time, this means performing the action of the tag,
|
||||
# such as defining a shape (registering it with our shape list) or adding/removing an object.
|
||||
@ -253,6 +475,7 @@ class AFPRenderer(VerboseOutput):
|
||||
new_mult_color,
|
||||
new_add_color,
|
||||
new_blend,
|
||||
obj.mask,
|
||||
newobj,
|
||||
)
|
||||
|
||||
@ -267,6 +490,7 @@ class AFPRenderer(VerboseOutput):
|
||||
new_mult_color,
|
||||
new_add_color,
|
||||
new_blend,
|
||||
obj.mask,
|
||||
newobj,
|
||||
)
|
||||
operating_clip.placed_objects[i] = new_clip
|
||||
@ -282,6 +506,7 @@ class AFPRenderer(VerboseOutput):
|
||||
new_mult_color,
|
||||
new_add_color,
|
||||
new_blend,
|
||||
obj.mask,
|
||||
newobj,
|
||||
)
|
||||
|
||||
@ -320,6 +545,7 @@ class AFPRenderer(VerboseOutput):
|
||||
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
|
||||
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
|
||||
tag.blend or 0,
|
||||
None,
|
||||
newobj,
|
||||
)
|
||||
)
|
||||
@ -327,14 +553,6 @@ class AFPRenderer(VerboseOutput):
|
||||
# Didn't place a new clip, changed the parent clip.
|
||||
return None, True
|
||||
elif isinstance(newobj, RegisteredClip):
|
||||
# TODO: Handle ON_LOAD triggers for this object. Many of these are just calls into
|
||||
# the game to set the current frame that we're on, but sometimes its important.
|
||||
for flags, code in tag.triggers.items():
|
||||
for bytecode in code:
|
||||
print("WARNING: Unhandled PLACE_OBJECT trigger!")
|
||||
if self.verbose:
|
||||
print(bytecode.decompile())
|
||||
|
||||
placed_clip = PlacedClip(
|
||||
tag.object_id,
|
||||
tag.depth,
|
||||
@ -343,10 +561,18 @@ class AFPRenderer(VerboseOutput):
|
||||
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
|
||||
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
|
||||
tag.blend or 0,
|
||||
None,
|
||||
newobj,
|
||||
)
|
||||
operating_clip.placed_objects.append(placed_clip)
|
||||
|
||||
for flags, code in tag.triggers.items():
|
||||
if flags & AP2Trigger.ON_LOAD:
|
||||
for bytecode in code:
|
||||
self.__execute_bytecode(bytecode, placed_clip)
|
||||
else:
|
||||
print("WARNING: Unhandled PLACE_OBJECT trigger with flags {flags}!")
|
||||
|
||||
# Placed a new clip, changed the parent.
|
||||
return placed_clip, True
|
||||
elif isinstance(newobj, RegisteredDummy):
|
||||
@ -359,6 +585,7 @@ class AFPRenderer(VerboseOutput):
|
||||
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
|
||||
tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
|
||||
tag.blend or 0,
|
||||
None,
|
||||
newobj,
|
||||
)
|
||||
)
|
||||
@ -402,13 +629,14 @@ class AFPRenderer(VerboseOutput):
|
||||
if not removed_objects:
|
||||
print(f"WARNING: Couldn't find object to remove by ID {tag.object_id} and depth {tag.depth}!")
|
||||
|
||||
# TODO: Handle ON_UNLOAD triggers for this object. I don't think I've ever seen one
|
||||
# on any object so this might be a pedantic request.
|
||||
|
||||
# Didn't place a new clip, changed parent clip.
|
||||
return None, True
|
||||
|
||||
elif isinstance(tag, AP2DoActionTag):
|
||||
print("WARNING: Unhandled DO_ACTION tag!")
|
||||
if self.verbose:
|
||||
print(tag.bytecode.decompile())
|
||||
self.__execute_bytecode(tag.bytecode, operating_clip)
|
||||
|
||||
# Didn't place a new clip.
|
||||
return None, False
|
||||
@ -457,6 +685,9 @@ class AFPRenderer(VerboseOutput):
|
||||
if parent_blend not in {0, 1, 2} and blend in {0, 1, 2}:
|
||||
blend = parent_blend
|
||||
|
||||
if renderable.mask:
|
||||
print(f"WARNING: Unsupported mask Rectangle({renderable.mask})!")
|
||||
|
||||
# Render individual shapes if this is a sprite.
|
||||
if isinstance(renderable, PlacedClip):
|
||||
if only_depths is not None:
|
||||
@ -553,6 +784,22 @@ class AFPRenderer(VerboseOutput):
|
||||
# Track whether anything in ourselves or our children changes during this processing.
|
||||
changed = False
|
||||
|
||||
while True:
|
||||
# See if this clip should actually be played.
|
||||
if clip.requested_frame is None and not clip.playing:
|
||||
break
|
||||
|
||||
# See if we need to fast forward to a frame.
|
||||
if clip.requested_frame is not None:
|
||||
if clip.frame > clip.requested_frame:
|
||||
# Rewind this clip to the beginning so we can replay until the requested frame.
|
||||
clip.placed_objects = []
|
||||
clip.frame = 0
|
||||
elif clip.frame == clip.requested_frame:
|
||||
# We played up through the requested frame, we're done!
|
||||
clip.requested_frame = None
|
||||
break
|
||||
|
||||
# Clips that are part of our own placed objects which we should handle.
|
||||
child_clips = [c for c in clip.placed_objects if isinstance(c, PlacedClip)]
|
||||
|
||||
@ -578,6 +825,10 @@ class AFPRenderer(VerboseOutput):
|
||||
# Now, advance the frame for this clip.
|
||||
clip.advance()
|
||||
|
||||
# See if we should bail.
|
||||
if clip.requested_frame is None:
|
||||
break
|
||||
|
||||
self.vprint(f"{prefix}Finished handling placed clip {clip.object_id} at depth {clip.depth}")
|
||||
|
||||
# Return if anything was modified.
|
||||
@ -687,12 +938,14 @@ class AFPRenderer(VerboseOutput):
|
||||
Color(1.0, 1.0, 1.0, 1.0),
|
||||
Color(0.0, 0.0, 0.0, 0.0),
|
||||
0,
|
||||
None,
|
||||
RegisteredClip(
|
||||
None,
|
||||
swf.frames,
|
||||
swf.tags,
|
||||
),
|
||||
)
|
||||
self.__movie = Movie(root_clip)
|
||||
|
||||
# These could possibly be overwritten from an external source of we wanted.
|
||||
actual_mult_color = Color(1.0, 1.0, 1.0, 1.0)
|
||||
@ -726,4 +979,7 @@ class AFPRenderer(VerboseOutput):
|
||||
# Allow ctrl-c to end early and render a partial animation.
|
||||
print(f"WARNING: Interrupted early, will render only {len(frames)}/{len(root_clip.source.frames)} frames of animation!")
|
||||
|
||||
# Clean up
|
||||
self.movie = None
|
||||
|
||||
return int(spf * 1000.0), frames
|
||||
|
@ -8,6 +8,7 @@ from .types import Matrix, Color, Point, Rectangle
|
||||
from .types import (
|
||||
AP2Action,
|
||||
AP2Tag,
|
||||
AP2Trigger,
|
||||
DefineFunction2Action,
|
||||
InitRegisterAction,
|
||||
StoreRegisterAction,
|
||||
@ -1235,33 +1236,33 @@ class SWF(TrackedCoverage, VerboseOutput):
|
||||
self.add_coverage(dataoffset + evt_offset, 8)
|
||||
|
||||
events: List[str] = []
|
||||
if evt_flags & 0x1:
|
||||
if evt_flags & AP2Trigger.ON_LOAD:
|
||||
events.append("ON_LOAD")
|
||||
if evt_flags & 0x2:
|
||||
if evt_flags & AP2Trigger.ON_ENTER_FRAME:
|
||||
events.append("ON_ENTER_FRAME")
|
||||
if evt_flags & 0x4:
|
||||
if evt_flags & AP2Trigger.ON_UNLOAD:
|
||||
events.append("ON_UNLOAD")
|
||||
if evt_flags & 0x8:
|
||||
if evt_flags & AP2Trigger.ON_MOUSE_MOVE:
|
||||
events.append("ON_MOUSE_MOVE")
|
||||
if evt_flags & 0x10:
|
||||
if evt_flags & AP2Trigger.ON_MOUSE_DOWN:
|
||||
events.append("ON_MOUSE_DOWN")
|
||||
if evt_flags & 0x20:
|
||||
if evt_flags & AP2Trigger.ON_MOUSE_UP:
|
||||
events.append("ON_MOUSE_UP")
|
||||
if evt_flags & 0x40:
|
||||
if evt_flags & AP2Trigger.ON_KEY_DOWN:
|
||||
events.append("ON_KEY_DOWN")
|
||||
if evt_flags & 0x80:
|
||||
if evt_flags & AP2Trigger.ON_KEY_UP:
|
||||
events.append("ON_KEY_UP")
|
||||
if evt_flags & 0x100:
|
||||
if evt_flags & AP2Trigger.ON_DATA:
|
||||
events.append("ON_DATA")
|
||||
if evt_flags & 0x400:
|
||||
if evt_flags & AP2Trigger.ON_PRESS:
|
||||
events.append("ON_PRESS")
|
||||
if evt_flags & 0x800:
|
||||
if evt_flags & AP2Trigger.ON_RELEASE:
|
||||
events.append("ON_RELEASE")
|
||||
if evt_flags & 0x1000:
|
||||
if evt_flags & AP2Trigger.ON_RELEASE_OUTSIDE:
|
||||
events.append("ON_RELEASE_OUTSIDE")
|
||||
if evt_flags & 0x2000:
|
||||
if evt_flags & AP2Trigger.ON_ROLL_OVER:
|
||||
events.append("ON_ROLL_OVER")
|
||||
if evt_flags & 0x4000:
|
||||
if evt_flags & AP2Trigger.ON_ROLL_OUT:
|
||||
events.append("ON_ROLL_OUT")
|
||||
|
||||
bytecode_offset += evt_offset
|
||||
|
@ -4,6 +4,7 @@ from .ap2 import (
|
||||
AP2Action,
|
||||
AP2Object,
|
||||
AP2Pointer,
|
||||
AP2Trigger,
|
||||
DefineFunction2Action,
|
||||
Expression,
|
||||
GenericObject,
|
||||
@ -39,6 +40,7 @@ __all__ = [
|
||||
'AP2Action',
|
||||
'AP2Object',
|
||||
'AP2Pointer',
|
||||
'AP2Trigger',
|
||||
'DefineFunction2Action',
|
||||
'Expression',
|
||||
'GenericObject',
|
||||
|
@ -2935,3 +2935,20 @@ class AP2Pointer:
|
||||
display_P = 0xb4
|
||||
geom_P = 0xb5
|
||||
filtesr_P = 0xb6
|
||||
|
||||
|
||||
class AP2Trigger:
|
||||
ON_LOAD = 0x1
|
||||
ON_ENTER_FRAME = 0x2
|
||||
ON_UNLOAD = 0x4
|
||||
ON_MOUSE_MOVE = 0x8
|
||||
ON_MOUSE_DOWN = 0x10
|
||||
ON_MOUSE_UP = 0x20
|
||||
ON_KEY_DOWN = 0x40
|
||||
ON_KEY_UP = 0x80
|
||||
ON_DATA = 0x100
|
||||
ON_PRESS = 0x400
|
||||
ON_RELEASE = 0x800
|
||||
ON_RELEASE_OUTSIDE = 0x1000
|
||||
ON_ROLL_OVER = 0x2000
|
||||
ON_ROLL_OUT = 0x4000
|
||||
|
Loading…
Reference in New Issue
Block a user